In rspec-mocks, what is the difference between allow(obj).to receive(:msg) and expect(obj).to receive(:msg)?
answer
- one permits, one demands
- allowed message returns nil
- checked when the example ends
- must be set before the call
- exactly once unless told otherwise
basics
~20 sallow(obj).to receive(:msg) permits a message and configures its reply; the example passes whether or not it is sent. expect(obj).to receive(:msg) also demands it: unless it arrives exactly once by the end of the example, the example fails.
solid answer
~40 sBoth take the same `receive(:msg)` object with the same fluent interface (`with`, `and_return`, `and_raise`, count constraints), and both work on a plain `double` or on a real object. `allow` only **permits** the message: with no response configured it returns `nil`, and nothing checks whether it was ever sent. `expect(...).to receive` **also sets a message expectation** that rspec-mocks verifies when the example finishes: by default the message must arrive **exactly once**, otherwise the failure reads `expected: 1 time ... received: 0 times`. Because the expectation is registered up front, it has to be written **before** the code under test runs. Use `allow` for a collaborator whose answer you need, `expect` only for the call that is the behaviour you are testing, and `expect(...).not_to receive` when a message must never be sent.
code
ruby · 14 linesRSpec.describe NotificationService do
let(:gateway) { double("sms gateway") }
let(:service) { NotificationService.new(gateway:) }
it "texts the user" do
expect(gateway).to receive(:deliver).with(to: "+15550100", body: "Hi")
service.notify(phone: "+15550100", text: "Hi")
end
it "records the receipt the gateway returns" do
allow(gateway).to receive(:deliver).and_return("msg-42")
expect(service.notify(phone: "+15550100", text: "Hi")).to eq("msg-42")
end
endgo deeper
Remember the one-line rule: allow permits and supplies an answer, expect also demands the call. Know that an un-configured stub returns nil and that expect must be set before the action.
Explain that expect registers a message expectation verified when the example ends, that the default count is exactly once, and how with, once, twice and at_least change what is checked.
Show judgement about how many expects an example carries: one demanded call per behaviour, allow for everything incidental, so refactors that change call counts do not break unrelated examples.
Frame the choice as a suite-wide convention: reviewers should question examples whose message expectations restate the implementation, because that coupling is what makes a large RSpec suite expensive to change.
## Two verbs, one message builder rspec-mocks, the mocking library that ships with RSpec 3.13, lets a spec configure how an object reacts to a **message** (a method call). The configuration always has the same shape: ```ruby allow(gateway).to receive(:deliver).and_return(true) expect(gateway).to receive(:deliver).with(to: "+15550100", body: "Hi") ``` `receive(:deliver)` builds the description of the message; the verb in front of it decides what RSpec does with that description. The object can be a **pure test double** made with `double` or `instance_double`, or a **partial double**, meaning a real object (or class) with just this one method replaced for the duration of the example. ## What allow does `allow(obj).to receive(:msg)` **permits** the message: - a strict `double` that would otherwise raise `received unexpected message :msg` now accepts it; - the reply is whatever you configure (`and_return`, `and_raise`, a block), and **`nil`** if you configure nothing; - nothing is checked at the end of the example; zero calls or fifty calls both pass. That makes `allow` the tool for a collaborator whose *answer* the code under test needs, such as a gateway that must return a delivery receipt so the service reaches its next branch. ## What expect ... to receive adds `expect(obj).to receive(:msg)` does everything `allow` does and also registers a **message expectation**. When the example completes, rspec-mocks verifies every registered expectation: 1. by default the message must have been received **exactly once**; zero calls and two calls both fail; 2. `with(...)` constrains the arguments; a call with other arguments fails, reported as `with unexpected arguments`, unless an `allow` stub for the same message catches it; 3. count constraints change the rule: `once`, `twice`, `thrice`, `exactly(3).times`, `at_least(:once)`, `at_most(2).times`. A failed expectation is reported like this: ``` (Double "sms gateway").deliver(*(any args)) expected: 1 time with any arguments received: 0 times with any arguments ``` `expect(obj).not_to receive(:msg)` is the negative form: any receipt of the message fails the example immediately. ## Order matters: expectation first, action second Because `expect ... to receive` installs its expectation when that line runs, it must come **before** the code under test. Written after the call, it installs a fresh expectation that nothing will satisfy, and the example fails with `received: 0 times` even though the call really happened. If you prefer to assert after acting, the spy style (`allow` or `spy`, then `expect(obj).to have_received(:msg)`) exists for exactly that. ## Side by side | | `allow(obj).to receive(:msg)` | `expect(obj).to receive(:msg)` | |---|---|---| | Unexpected-message error on a double | suppressed | suppressed | | Default return value | `nil` | `nil` | | Verified at end of example | no | yes, exactly once by default | | Placement | anywhere before the call | before the call | | Typical use | supply a collaborator's answer | assert the call that *is* the behaviour | ## How a failure surfaces When a strict double receives a message nobody allowed, or a message expectation is not met, rspec-mocks raises `RSpec::Mocks::MockExpectationError`. That class inherits from **`Exception`**, not `StandardError`, which matters for code under test that protects itself with a bare `rescue => e`: - a bare `rescue` only catches `StandardError` and its subclasses; - so a service that wraps its gateway call in `rescue => e` and logs the error does **not** swallow the mock failure; - the example still fails with the rspec-mocks message instead of passing silently. Unmet `expect ... to receive` expectations are checked after the example body has run, so the failure is reported against the line that set the expectation, which is why reading the `Failure/Error:` line points you at the `expect`, not at the production call. ## Choosing between them A useful rule is that each example should demand only what it is about. In a notification-service spec, the example "sends the text through the SMS gateway" earns one `expect(gateway).to receive(:deliver)`; every other example that merely needs the gateway to cooperate uses `allow`. Turning every `allow` into `expect` welds the spec to the implementation: harmless refactors that change how often a collaborator is asked start failing examples that were never about that call. ## Common mistakes - Writing `expect(...).to receive` after the action and expecting it to check the past. - Believing `allow` fails when the message is never sent; it never checks. - Forgetting that an un-configured stub returns `nil`, then chasing a `NoMethodError` on `nil` further down. - Assuming a plain `expect(...).to receive` accepts any number of calls; the default is exactly one.
- What happens if the code under test calls a message twice that was set up with a plain expect(obj).to receive(:msg)?The example fails. A message expectation with no count constraint expects exactly one call, so the second call leaves the actual count at 2 and verification at the end of the example reports `expected: 1 time ... received: 2 times`. Add `twice`, `exactly(n).times` or `at_least(:once)` when more than one call is legitimate.
- Can allow and expect be combined on the same message in one example?Yes. A common pattern is a broad `allow(gateway).to receive(:deliver).and_return(true)` in a `before` hook, then `expect(gateway).to receive(:deliver).with(to: "+15550100", body: anything)` inside the one example that is about that call. The expectation is checked, and calls it does not match fall back to the allowed stub.
saying these in an interview costs you the question
- allow(...).to receive fails the example when the message is never sent
- expect(...).to receive can be written after the action and still check it
- an allowed message with no and_return returns the real method's result
- a plain expect(...).to receive accepts any number of calls
- every collaborator call should be an expect so nothing slips through