skip to content

An RSpec 3.13 spec stubs SmsGateway.deliver on the real class and still passes after deliver was renamed; what does verify_partial_doubles change, and what does it still miss?

level: seniorimportance: should knowfreq 38%

answer

  1. partial double = real object, one method replaced
  2. rspec-mocks default: false
  3. generated spec_helper sets true
  4. existence and arity checked
  5. without_partial_double_verification escape hatch

basics

~20 s

With verify_partial_doubles off, which is rspec-mocks' default in RSpec 3.13, a real object can be stubbed with methods it no longer has. Turned on, stubs are checked for existence and argument signature; return values and pure doubles stay unchecked.

solid answer

~50 s

A **partial double** is a real object or class with one method replaced for the example, as in `allow(SmsGateway).to receive(:deliver)`. rspec-mocks' `verify_partial_doubles` setting is **false** by default in RSpec 3.13, so that stub installs happily even when `SmsGateway.deliver` no longer exists, and the spec keeps passing while production breaks. Setting `mocks.verify_partial_doubles = true` in `RSpec.configure` (the `spec_helper.rb` that `rspec --init` generates already does, and its comment says RSpec 4 will make it the default) applies `object_double`-style checks: the method must exist on the object, and calls and `with` constraints must fit its parameters. It still does not check what `and_return` returns, it rejects methods that do not exist yet when the stub is declared, such as ones defined later at run time (wrap those stubs in `without_partial_double_verification`), and it does nothing for plain `double`s, which need `instance_double`.

go deeper

for a junior

Know what a partial double is, a real object with one method stubbed, and that RSpec can check such stubs against the real method once verify_partial_doubles is on.

for a middle

Explain that the rspec-mocks default is false, that the generated spec_helper sets it true, and that it checks method existence plus call and with signatures.

for a senior

Diagnose a green spec over a renamed method by checking this setting first, roll it out to an old suite, and use without_partial_double_verification narrowly for dynamic methods.

for a principal

Treat verification settings as a suite-wide contract policy, and pair them with a few real-collaborator specs, since signatures alone cannot prove the gateway still behaves as the doubles claim.

## The failure mode A notification service calls `SmsGateway.deliver(to:, body:)`. Its spec avoids real network traffic with a **partial double**, a real class with one method replaced for the duration of the example: ```ruby allow(SmsGateway).to receive(:deliver).and_return("msg-42") ``` A refactor renames the class method to `SmsGateway.send_message`. Production now raises `NoMethodError` on every notification, yet the spec stays green: rspec-mocks installed a `deliver` method on `SmsGateway` for the example, so the code under test found exactly what it expected. The stub hid a real interface change. ## What the setting does `verify_partial_doubles` is an rspec-mocks configuration option: ```ruby RSpec.configure do |config| config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end end ``` With it on, the rspec-mocks documentation says partial doubles get the same checks as `object_double`: 1. **Existence**: stubbing or expecting a method the object does not respond to fails when the stub is declared, with an error such as `SmsGateway does not implement: deliver`. A private method counts as existing, because the check asks `respond_to?(name, true)`. 2. **Call signature**: each call on the stubbed method is checked against the original method's parameters, so a missing required keyword raises `ArgumentError`. 3. **`with` constraints**: an argument list the real method could never accept fails when the expectation is declared. ## How the check decides that a method exists For a partial double there is a real object to ask, so rspec-mocks calls `respond_to?(name, true)` on it. Two consequences follow: - a method answered through `method_missing` counts as existing **if** the object's `respond_to_missing?` says so, unlike `instance_double`, which can only ask the class; - private methods count as existing, so verification does not stop you stubbing a private helper, which is usually a spec-design smell rather than something the setting polices. The signature check then reads the original method's parameters: required and optional positionals, required keywords, and whether it takes a splat. A method defined as `def deliver(*args)` accepts anything, so the arity check has nothing to catch. ## Version facts | Fact | RSpec 3.13 | |---|---| | Default inside rspec-mocks | `false` | | Generated `spec/spec_helper.rb` | sets it to `true` | | Stated plan | "will default to `true` in RSpec 4" (comment in the generated file) | The documentation gives the reason it defaults to off: backwards compatibility. Older suites that never ran `rspec --init` with a recent RSpec, or that replaced the generated file, may not have it, and that is worth checking first when a stubbed method turns out not to exist. ## What it still misses Verification compares names and signatures, nothing more: - **Return values** are unchecked. `and_return("msg-42")` passes even if the real method now returns a receipt object. - **Behaviour** is unchecked. A method that still exists but now raises for your inputs will not be noticed. - **Dynamically defined methods** that appear only at run time can make verification fail spuriously. `without_partial_double_verification { ... }` turns the check off just for the stubs declared inside the block, instead of for the whole suite. - **Pure doubles** are out of scope. A plain `double` is not a partial double; only `instance_double`, `class_double` and `object_double` verify it. - **String-named verifying doubles** verify only when the constant is loaded; `verify_doubled_constant_names = true` makes an undefined name an error, and the rspec-mocks docs recommend it for full-suite runs. ## Rolling it out to an old suite Turning the setting on in a large suite usually surfaces a batch of failures at once. Each is one of three things: 1. a **stale stub** for a method that was renamed or removed, which is the bug the setting exists to find; 2. an **arity mismatch**, often a stub whose `with` no longer matches the real keywords; 3. a **dynamic method** defined after the object is created, which calls for `without_partial_double_verification` around that stub or for defining the method explicitly. Fixing the first two is ordinary maintenance. The third should stay rare; if it is common, that is a sign the spec stubs framework internals rather than your own interfaces. ## Beyond configuration Even with every verification switch on, a green unit suite proves only that the stubs match signatures. A small number of specs that exercise the real `SmsGateway` against a sandbox or a recorded fixture are what prove the contract.

  • A spec stubs a method that a library defines on instances only at run time, and verify_partial_doubles makes it fail. How do you handle it without turning the setting off?
    Wrap just that stub in `without_partial_double_verification { allow(obj).to receive(:dynamic_reader).and_return(1) }`. The helper suppresses partial-double verification for the duration of the block and restores it afterwards, so every other stub in the suite stays verified. If the method is yours, defining it explicitly is the cleaner fix.
  • Does verify_partial_doubles make a plain double("sms gateway") reject unknown methods?
    No. It applies only to partial doubles, meaning real objects and classes with stubbed methods. A plain `double` has no real counterpart to check against; replace it with `instance_double(SmsGateway)` or `class_double(SmsGateway)` to get existence and signature checks.

saying these in an interview costs you the question

  • verify_partial_doubles is on by default inside rspec-mocks 3.13
  • verify_partial_doubles also checks that stubbed return values have the right type
  • verify_partial_doubles makes plain double objects verify method names
  • the only fix for a dynamic method is turning verification off suite-wide
  • a green spec with stubbed classes proves the collaborator contract holds