Do not allow_any_instance_of (unless you must)
The RSpec command
allow_any_instance_of stubs a specific method on every instance of a class. This is dangerous in that you could accidentally reference the wrong instance of the object in your test, which could lead to the wrong result. As such, the RSpec team discourages use of this command.
But, if that's reason enough for the RSpec team to recommend not using it, why did they include it? And, what's the alternative?
Trust me, there are good reasons. But, before I get there, let me explain the alternative. Consider this code:
class Foo def bar baz = Baz.new(1) baz.qux end end class Baz def initialize(value) @value = value end def qux @value end end
Say I want to stub the
qux method on the
baz instance? I can do that with
allow_any_instance_of. But, what if we need multiple instances of
Baz inside the
class Foo def bar baz1 = Baz.new(1) baz2 = Baz.new(2) baz1.qux + baz2.qux end end
Now, when we
allow_any_instance_of(Baz).to receive(:qux), we're allowing both instances to receive the message. Maybe that's okay for some use cases. But, a more testable design would pass the instances of
Baz into the bar method or the
Foo#initialize method. This technique is called dependency injection. It looks like this:
# Constructor injection class Foo def initialize(baz1, baz2) @baz1, @baz2 = baz1, baz2 end def bar @baz1.qux + @baz2.qux end end # Calling code Foo.new(Baz.new(1), Baz.new(2)).bar
Now, your test can pass in actual
Baz objects (if they're lightweight) or mock them individually (if they're costly) rather than using the crude club of
So, why wouldn't folks just always use dependency injection?
Well, it turns out that some frameworks make that impossible. Ruby on Rails, for example, hides the creation of controller objects from the developer. It also calls the controller actions for you. So, there's no opportunity to inject dependencies into the controller either through the constuctor or as parameters to the method. Therefore, you must call
.new to create any dependencies in the controller actions themselves, which leaves you with little choice but to use
allow_any_instance_of to test the calls to methods on those objects.
(Well, technically, you could also stub
Baz.new to return an
instance_double that stubs the
qux method. But, now you're all up in the implementation, which makes refactoring that code much harder, because you'll break all your tests. Don't do that either.)
Finally, legacy code – code without tests – is often written without dependency injection. So,
allow_any_instance_of allows you to put tests around the existing code before you begin refactoring to a more testable implementation.