In the 2.11 release, rspec-mocks is gaining a significant new capability that, as far as I know, isn’t provided by any other ruby mocking library: constant stubbing1.
Let’s look at the API, and then talk a bit about some of the use cases for it.
The main API is
This works for both defined and undefined constants; you could stub
A::B::C::D::E::F even if none of the intermediary constants exist. When the example completes, the constants will be restored to their original states: any newly defined constants will be undefined, and any modified constants will be restored to their original values.
Note that constant names must be fully qualified; the current module nesting is not considered:
stub_const also supports a
:transfer_nested_constants option. Consider a case where you have nested constants:
stub_const("CardDeck", fake_class) cuts off access to the nested constants (
CardDeck::NUM_CARDS), unless you manually assign
:transfer_nested_constants option is provided to take care of this for you:
I’ve found this useful in a few different situations:
- It provides a simple way to change a class setting expressed as a constant for one test. In the past, I’ve often defined static class methods just so they could be stubbed, even though it made more sense to use a constant. Now you can just use a constant!
- It makes dependency injection easy when the class-under-test depends on a collaborator’s class method (e.g. when the collaborator is stateless). You can easily stub the collaborator’s class constant with a test double.
- It makes stubbing unloaded dependencies dead-simple. Gary Bernhardt discussed this situation at length in Destroy all Software #46. He mentioned mutating constants as a possible way of stubbing unloaded dependencies, but recommended against it because of the complexity of safely managing this. Now that rspec-mocks can do it for you, it’s far less complex, and much, much safer.
If you’re curious how it all works, check out the source on github.