In rspec-mocks, what do and_return, and_raise and and_call_original make a stubbed message do, and where is and_call_original unavailable?
answer
- several values, last one repeats
- class, class plus message, or instance
- delegate to the untouched method
- pure test double has no original
- and_wrap_original decorates it
basics
~20 sand_return sets the reply, and with several values returns them in order, repeating the last. and_raise raises an exception class or instance. and_call_original runs the real method, so it works only on partial doubles, never on a pure double.
solid answer
~50 sAll three are chained onto `receive(:msg)` after `allow` or `expect` and end the chain. `and_return(a, b)` answers `a`, then `b`, then `b` on every later call. `and_raise` accepts an exception class, a class plus message, or an exception instance; pass an instance when the class's constructor needs more than a message. `and_call_original` delegates to the method that was there before the stub, which is useful to **observe** a real call with `expect` or to stub only some arguments with `with` while the rest run for real. It needs an original, so it works only on **partial doubles** (real objects or classes): on a `double` it fails with `is a pure test double. and_call_original is only available on a partial double.` `and_wrap_original` is the variant that receives the original method and lets you alter its arguments or result.
go deeper
Recall the three responses: and_return for a value, and_raise for an exception, and_call_original to run the real method. The last one only works on a real object.
Explain the ordering of multiple and_return values, the three and_raise forms and why a multi-argument exception needs an instance, and the default-plus-override pattern with and_call_original and with.
Use and_call_original to observe calls without altering behaviour, and be ready to explain why it errors on pure doubles and when and_wrap_original is the cleaner tool.
Question specs that stub real classes heavily with and_call_original: they blur isolation, so decide as a team where partial doubles are acceptable and where an injected verifying double is clearer.
## Where responses fit In rspec-mocks (RSpec 3.13) a stub or message expectation is built as `allow(obj).to receive(:msg)` or `expect(obj).to receive(:msg)`. Without anything more, the message returns `nil`. **Response methods** chained onto `receive` decide what happens instead. Each of the three below returns `nil`, so it must be the last link in the chain: put `with`, `once` or `twice` before it. ## and_return `and_return(value)` answers every call with `value`. Given several values, it hands them out **in order** and then keeps repeating the **last one**: ```ruby allow(gateway).to receive(:deliver).and_return(:queued, :sent) gateway.deliver # => :queued gateway.deliver # => :sent gateway.deliver # => :sent ``` Two details trip people up: - `and_return` does not accept an implementation block; passing one raises `ArgumentError` (use a block on `receive` itself, or `and_invoke` with lambdas, when the answer must be computed). - On an `expect`, supplying several values raises the expected call count to the number of values unless you give an explicit count, so `expect(...).to receive(:deliver).and_return(:queued, :sent)` wants two calls. ## and_raise `and_raise` makes the message raise, which is how a notification-service spec reaches its error-handling branch when the SMS gateway times out. The accepted forms are: 1. `and_raise` with no argument raises a `RuntimeError`; 2. `and_raise(GatewayTimeout)` raises a new instance of the class; 3. `and_raise(GatewayTimeout, "no ack after 5s")` adds a message; 4. `and_raise(GatewayTimeout.new(retry_after: 30))` raises the instance you built. The class forms let Ruby's `raise` build the exception with a message at most, so a custom exception whose constructor requires other arguments must be passed as an **instance**. Otherwise, when the stubbed message is called, Ruby cannot build the exception and raises `ArgumentError` instead of your error, and a `rescue GatewayTimeout` in the service never fires. `and_throw(:symbol)` is the separate response for Ruby's non-error `throw`/`catch` unwinding. ## and_call_original `and_call_original` tells the stub to delegate to the **original, unmodified method**. It is valuable in two situations: - **Observing without changing.** `expect(SmsGateway).to receive(:deliver).and_call_original` checks that the call happened while the real method still runs. - **Default plus override.** `allow(Rates).to receive(:for).and_call_original` followed by `allow(Rates).to receive(:for).with("GB").and_return(0.05)` stubs one argument and lets every other call through. The method only makes sense where an original exists. That means **partial doubles**: real objects or classes on which one method has been replaced for the example. A pure test double from `double`, `instance_double` or `spy` has no original implementation, and rspec-mocks rejects the call with `#<Double "sms gateway"> is a pure test double. and_call_original is only available on a partial double.` `and_wrap_original` is the decorating sibling. Its block receives the original method plus the call's arguments, so it can change the arguments, post-process the result, or both. The same partial-double restriction applies. ## Computed answers: blocks and and_invoke When the reply depends on the arguments, the response is a block rather than a value: - a block passed to `receive` (or chained with `{ }` after it) runs on every call and receives the call's arguments, for example `allow(gateway).to receive(:status) { |message_id| message_id.start_with?("msg-") ? :sent : :unknown }`; - `and_invoke(first, second)` takes callables and, like `and_return`, uses them in order and repeats the last, which is handy for "time out once, then succeed"; - a block is also where you can raise conditionally, since `and_raise` always raises. ## Response methods at a glance | Method | Effect | Works on a pure double? | |---|---|---| | `and_return(a, b)` | returns `a`, then `b` repeatedly | yes | | `and_raise(Klass, msg)` | raises the exception | yes | | `and_throw(:sym)` | throws a symbol | yes | | `and_yield(x)` | yields `x` to the caller's block | yes | | `and_call_original` | runs the real method | no, partial doubles only | | `and_wrap_original { ... }` | wraps the real method | no, partial doubles only | ## Pitfalls to name in an interview - Expecting `and_call_original` to work on `instance_double`: verification checks names, but there is still no original to run. - Chaining `.once` after `and_return`: the response method returns `nil`, so the chain breaks. - Using `and_raise(CustomError)` for an error class with a multi-argument constructor instead of passing an instance. - Assuming the last `and_return` value is used only once; it repeats for every later call.
- How would you stub a class method for one argument while every other argument still runs the real code?Set a default that delegates, then a narrower stub: `allow(Rates).to receive(:for).and_call_original` and `allow(Rates).to receive(:for).with("GB").and_return(0.05)`. rspec-mocks matches the more specific `with` stub for `"GB"` and falls back to the original method for everything else. This only works on a partial double such as a real class.
- When would you choose and_wrap_original over and_call_original?When you need the real behaviour but must change it slightly, such as trimming a large result or forcing an argument. `and_wrap_original` yields the original method object and the call's arguments to your block, so you can call `original.call(*args)` and alter what comes back. Like `and_call_original`, it is available only on partial doubles.
saying these in an interview costs you the question
- and_call_original works on any double, including instance_double
- and_return with several values cycles back to the first value
- and_raise can only take an exception class, never an instance
- you can chain .once after and_return to limit it
- and_raise(SomeError) supplies whatever arguments the error's constructor requires