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 Foo#bar
method?
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 allow_any_instance_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.
No spam, no sharing to third party. Only you and me.
Member discussion