Do not allow_any_instance_of (unless you must)

The RSpec team says not to use `allow_any_instance_of`. So, why did they include it in RSpec in the first place?
Do not allow_any_instance_of (unless you must)
Photo by David Guenther / Unsplash

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.

Subscribe to my occasional newsletter

No spam, no sharing to third party. Only you and me.

Member discussion