In Ruby, what does def gateway.charge(amount) do inside a test, and why do other Gateway instances keep the original charge method?
answer
- one object, one hidden class
- consulted before Gateway
- gateway.class is unchanged
- singleton_methods shows it
- other instances untouched
basics
~10 sdef gateway.charge defines a singleton method: it is stored in gateway's own singleton class, which Ruby checks before Gateway, so only that object uses it. Every other Gateway instance still finds Gateway#charge.
solid answer
~40 s`def gateway.charge(amount)` defines a **singleton method**, a method that belongs to one object. Ruby stores it in that object's **singleton class**, a hidden class created for the object on demand and placed in front of `Gateway` when looking up methods on `gateway` only. A call to `gateway.charge` finds the new definition first; `other.charge` never sees it, because `other` has no such singleton class entry. `gateway.class` still returns `Gateway`, and `is_a?` answers as before; `gateway.singleton_methods` returns `[:charge]` and reveals the change. Inside the new body, `super` reaches `Gateway#charge`. That is why the trick works as a quick, per-object stand-in in a test without touching the class.
code
ruby · 14 linesclass Gateway
def charge(amount) = raise("network call")
end
gateway = Gateway.new
other = Gateway.new
def gateway.charge(amount) = :approved
gateway.charge(10) # => :approved
gateway.class # => Gateway
gateway.singleton_methods # => [:charge]
other.singleton_methods # => []
other.charge(10) # RuntimeError: network callgo deeper
Recall that def obj.name defines a method on that one object, and that other instances and the object's class are unaffected.
Explain the singleton class placed in front of the class in lookup, why super reaches the class's method, and how singleton_methods reveals the change.
Use per-object methods only on objects the test owns, prefer a block-based definition when the stub needs test values, and recognise the leak risk on shared objects.
Set expectations on when hand-written singleton stubs are acceptable versus a test-double library that restores state automatically.
## What the syntax does `def` normally defines a method in a class or module. Written with an explicit receiver — `def gateway.charge(amount)` — it defines the method on **that one object**. Such a method is a **singleton method**, and it is ordinary Ruby, not a testing feature. ```ruby gateway = Gateway.new def gateway.charge(amount) = :approved gateway.charge(10) # => :approved Gateway.new.charge(10) # => whatever Gateway#charge returns ``` ## Where the method lives Every object can have a **singleton class**: a class that has exactly one instance, the object itself. Ruby creates it the first time something needs it, such as a singleton method definition. Its role in method lookup is simple: 1. When you call `gateway.charge`, Ruby first looks in `gateway`'s singleton class. 2. If the method is not there, it continues to `Gateway`, then to `Gateway`'s ancestors. Other instances have no entry for `charge` in a singleton class of their own, so their lookup goes straight to `Gateway`. That is the whole reason the change stays local. ## What stays the same - **`gateway.class`** still returns `Gateway`. The singleton class is hidden from `class`; you reach it with `gateway.singleton_class`. - **Type checks** such as `gateway.is_a?(Gateway)` are unchanged. - **Instance variables** and the rest of the object are untouched; only its method lookup gained a layer. ## What changes, and how to see it | Question | Before the `def` | After it | |---|---|---| | `gateway.singleton_methods` | `[]` | `[:charge]` | | `gateway.charge(10)` | runs `Gateway#charge` | runs the singleton method | | `other.charge(10)` | runs `Gateway#charge` | runs `Gateway#charge` | | `gateway.class` | `Gateway` | `Gateway` | Inside the singleton method, `super` calls the next definition in lookup, which is `Gateway#charge`, so a wrapper that logs and delegates is possible: ```ruby def gateway.charge(amount) puts "charging #{amount}" super end ``` ## Using it in a test Giving one object a custom method is a lightweight way to replace a collaborator's behaviour for a single test: - It needs no library and reads as plain Ruby. - It affects only the object you built for the test. - It cannot be undone easily, so it suits **objects created inside the test** and discarded afterwards, not shared ones. A method body written with `def` cannot see the test's local variables, because `def` starts a new scope. When the stub needs to return a value computed in the test, `define_singleton_method(:charge) { |amount| result }` takes a block that closes over `result`. ## def obj.x among the alternatives | Technique | Scope | Sees test locals? | |---|---|---| | `def gateway.charge` | this object only | no, `def` opens a new scope | | `gateway.define_singleton_method(:charge) { ... }` | this object only | yes, the block is a closure | | mixing a named module into this one object | this object only | no, the module is defined elsewhere | | a small subclass or stand-in class | every instance you build from it | no | The first two write into the same singleton class and differ only in how the body is written. A named module is reusable across tests and easier to recognise in a backtrace. A stand-in class avoids touching the real object at all, which is often the cleanest option when the collaborator is passed in. ## Mistakes to avoid - Believing `def gateway.charge` changes `Gateway` for every instance. - Expecting `gateway.class` to report a new class. - Using a local variable of the test inside a `def` body. - Applying the trick to a long-lived shared object, where the method stays for the rest of the process. ## The model to keep Each object can carry a private, hidden class holding methods that belong to it alone. `def obj.name` writes into that class; every call on `obj` checks it first. Class methods, `class << self` and `singleton_methods` are all views of this one mechanism.
- Inside def gateway.charge, what does a bare super call?The next `charge` in `gateway`'s method lookup, which is `Gateway#charge` (or whatever its ancestors provide). The singleton class sits in front of the class, so a singleton method can wrap the class's version: do something extra, then call `super`.
- Why does def gateway.charge = result fail to see a local result defined in the test?`def` opens a new scope that sees no outer locals. To return a value from the test, use `gateway.define_singleton_method(:charge) { |_amount| result }`; the block is a closure, so `result` stays visible.
A singleton method is like a sticky note on one printed copy of a manual: whoever reads that copy sees the note before the printed page, while every other copy stays as printed.
saying these in an interview costs you the question
- def gateway.charge redefines charge for every Gateway instance
- After def gateway.charge, gateway.class returns a new anonymous class
- A singleton method cannot call the class's version with super
- A def body inside a test can read the test's local variables
- Singleton methods are a testing-library feature, not core Ruby