In an RSpec spec, what does stub_const do, and why can stub_const("SmsGateway", fake) miss the constant the code actually uses?
answer
- restored when the example ends
- defines it if undefined
- fully qualified name only
- nested constants not transferred
- class_double(...).as_stubbed_const
basics
~10 sstub_const replaces or defines a constant for one example and restores it afterwards. Its name must be fully qualified: inside module Notifications, stub_const("SmsGateway", fake) stubs ::SmsGateway, not Notifications::SmsGateway.
solid answer
~30 s`stub_const("Notifications::SmsGateway", fake)` points the constant at `fake` for the current example; when the example ends rspec-mocks restores the original value, or removes the constant if it was undefined before. The name is resolved from the top level and the spec's module nesting is **not** considered, so a bare `"SmsGateway"` written inside `module Notifications` stubs `::SmsGateway` and the code keeps using the real `Notifications::SmsGateway`. Replacing a class also drops its nested constants (`SmsGateway::MAX_LENGTH`) unless you pass `transfer_nested_constants: true` or a list of names. `hide_const` makes a constant undefined for the example, and `class_double("Notifications::SmsGateway").as_stubbed_const` swaps in a verifying double in one step.
go deeper
Recall that stub_const swaps a constant's value for one example and puts it back afterwards, and that the name must be written in full.
Explain the fully-qualified-name rule, why an undefined name is silently defined, and how transfer_nested_constants and hide_const change the result.
Recognise a stub that misses because of nesting or an earlier captured reference, and prefer class_double(...).as_stubbed_const when class methods must stay verified.
Treat widespread constant stubbing as a design signal and steer the team toward injected collaborators, keeping stub_const for configuration values and genuinely global entry points.
## What stub_const is for Most collaborators should be injected, but some code reaches for a constant directly: a class method such as `Notifications::SmsGateway.deliver`, or a setting such as `Notifications::MAX_RETRIES`. rspec-mocks (RSpec 3.13) provides `stub_const` for those cases: ```ruby stub_const("Notifications::MAX_RETRIES", 1) stub_const("Notifications::SmsGateway", fake_gateway_class) ``` For the current example the constant refers to the new value. When the example completes, rspec-mocks restores it, and if the constant did **not exist** before, it is removed again. `stub_const` returns the stubbed value. ## Fully qualified names, always The argument is a String holding the **fully qualified** name. The rspec-mocks documentation states it plainly: the current constant scoping at the point of call is not considered. That produces a classic miss: ```ruby module Notifications RSpec.describe Dispatcher do it "uses the fake gateway" do stub_const("SmsGateway", fake) # defines ::SmsGateway # Dispatcher still resolves Notifications::SmsGateway end end end ``` Because `::SmsGateway` did not exist, `stub_const` quietly **defines** it, so nothing errors. The code under test resolves `SmsGateway` lexically inside `Notifications`, finds the real `Notifications::SmsGateway`, and the spec sends a real text or fails for an unrelated reason. The fix is to write `"Notifications::SmsGateway"`. ## Nested constants When the stubbed constant is a class or module, its **nested constants are not carried over** by default: | Call | `SmsGateway::MAX_LENGTH` afterwards | |---|---| | `stub_const("SmsGateway", Class.new)` | uninitialized constant | | `stub_const("SmsGateway", Class.new, transfer_nested_constants: true)` | original value | | `stub_const("SmsGateway", Class.new, transfer_nested_constants: [:MAX_LENGTH])` | original value, others missing | Transfer only works when both the original and the replacement are modules or classes. The suite-wide default can be changed with the rspec-mocks `transfer_nested_constants` configuration option, which is `false` out of the box. ## Related tools - **`hide_const("Notifications::SmsGateway")`** makes the constant undefined for the example, useful for testing a code path that checks `defined?` before using an optional dependency. - **`class_double("Notifications::SmsGateway").as_stubbed_const`** builds a verifying class double and installs it under that constant in one step, so class-method stubs are checked against the real class. - `as_stubbed_const` also accepts the `transfer_nested_constants` option. ## hide_const in practice `hide_const` is the mirror image. Suppose the notification service uses an optional SMS client only when its constant is defined: ```ruby def channel defined?(Notifications::SmsGateway) ? :sms : :email end ``` A spec for the fallback path calls `hide_const("Notifications::SmsGateway")`, and for that example `defined?` returns `nil`, so the service chooses `:email`. After the example, the constant is back. Hiding a constant that is already undefined is a no-op, which lets the same spec run both in isolation and in a full suite. ## Which tool for which constant | Goal | Call | |---|---| | change a setting for one example | `stub_const("Notifications::MAX_RETRIES", 1)` | | swap a class for a hand-built fake | `stub_const("Notifications::SmsGateway", fake)` | | swap a class for a verified double | `class_double("Notifications::SmsGateway").as_stubbed_const` | | pretend a constant does not exist | `hide_const("Notifications::SmsGateway")` | ## Limits worth knowing 1. `stub_const` changes what the **constant name** refers to. Code that copied the old value earlier, for example `GATEWAY = SmsGateway` evaluated when a file loaded, keeps the object it captured. 2. A stub of a whole class replaces every class method at once; if the code needs only one method replaced, a stub on the real class (a partial double) or `class_double(...).as_stubbed_const` is narrower. 3. Frequent `stub_const` use is often a design signal: injecting the gateway as an argument or keyword removes the need to rewrite a global name. ## Summary for an interview - `stub_const` is per-example and self-restoring. - Names are fully qualified; module nesting in the spec is ignored. - Stubbing an undefined name defines it silently, which is how the miss goes unnoticed. - Nested constants disappear unless transferred.
- Why does a mistyped stub_const name usually not raise an error?Because `stub_const` can stub constants that do not exist: it defines the name for the example and removes it afterwards. A typo or a missing namespace therefore creates a new, unused constant instead of failing. The only symptom is that the code under test still uses the real one, so check the fully qualified name when a stubbed constant seems to have no effect.
- When would you use class_double(...).as_stubbed_const instead of stub_const with a hand-built fake class?When the code calls class methods on the constant and you want those stubs verified. `class_double("Notifications::SmsGateway").as_stubbed_const` installs a verifying class double under the name, so stubbing a class method the real class lacks fails instead of passing against a fiction.
saying these in an interview costs you the question
- stub_const resolves the name relative to the spec's enclosing module
- a stubbed constant stays replaced for the rest of the suite
- stub_const raises when the named constant does not exist
- replacing a class with stub_const keeps its nested constants automatically
- hide_const deletes the constant permanently from the program