Any Instance
rspec-mocks provides two methods, allow_any_instance_of
and
expect_any_instance_of
, that will allow you to stub or mock any instance of a class. They
are used in place of allow
or expect
:
allow_any_instance_of(Widget).to receive(:name).and_return("Wibble")
expect_any_instance_of(Widget).to receive(:name).and_return("Wobble")
These methods add the appropriate stub or expectation to all instances of Widget
.
You can also configure the responses in the same manner.
This feature is sometimes useful when working with legacy code, though in general we discourage its use for a number of reasons:
- The
rspec-mocks
API is designed for individual object instances, but this feature operates on entire classes of objects. As a result there are some semantically confusing edge cases. For example, inexpect_any_instance_of(Widget).to receive(:name).twice
it isn't clear whether a specific instance is expected to receivename
twice, or if two receives total are expected. (It's the former.) - Using this feature is often a design smell. It may be that your test is trying to do too much or that the object under test is too complex.
- It is the most complicated feature of
rspec-mocks
, and has historically received the most bug reports. (None of the core team actively use it, which doesn't help.)
Use allow_any_instance_of
to stub a method
Given a file named "example_spec.rb" with:
RSpec.describe "allow_any_instance_of" do
it "returns the specified value on any instance of the class" do
allow_any_instance_of(Object).to receive(:foo).and_return(:return_value)
o = Object.new
expect(o.foo).to eq(:return_value)
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
Use allow_any_instance_of
to stub multiple methods
Given a file named "example_spec.rb" with:
RSpec.describe "allow_any_instance_of" do
context "with receive_messages" do
it "stubs multiple methods" do
allow_any_instance_of(Object).to receive_messages(:foo => 'foo', :bar => 'bar')
o = Object.new
expect(o.foo).to eq('foo')
expect(o.bar).to eq('bar')
end
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
Stubbing any instance of a class with specific arguments
Given a file named "example_spec.rb" with:
RSpec.describe "allow_any_instance_of" do
context "with arguments" do
it "returns the stubbed value when arguments match" do
allow_any_instance_of(Object).to receive(:foo).with(:param_one, :param_two).and_return(:result_one)
allow_any_instance_of(Object).to receive(:foo).with(:param_three, :param_four).and_return(:result_two)
o = Object.new
expect(o.foo(:param_one, :param_two)).to eq(:result_one)
expect(o.foo(:param_three, :param_four)).to eq(:result_two)
end
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
Block implementation is passed the receiver as first arg
Given a file named "example_spec.rb" with:
RSpec.describe "allow_any_instance_of" do
it 'yields the receiver to the block implementation' do
allow_any_instance_of(String).to receive(:slice) do |instance, start, length|
instance[start, length]
end
expect('string'.slice(2, 3)).to eq('rin')
end
end
When I run rspec example_spec.rb
Then the examples should all pass.
Use expect_any_instance_of
to set a message expectation on any instance
Given a file named "example_spec.rb" with:
RSpec.describe "expect_any_instance_of" do
before do
expect_any_instance_of(Object).to receive(:foo)
end
it "passes when an instance receives the message" do
Object.new.foo
end
it "fails when no instance receives the message" do
Object.new.to_s
end
end
When I run rspec example_spec.rb
Then it should fail with the following output:
| 2 examples, 1 failure | | Exactly one instance should have received the following message(s) but didn't: foo |
Specify different return values for multiple calls in combination with allow_any_instance_of
Using the multiple calls feature with allow_any_instance_of
results in the behaviour where multiple calls are configured on every instance.
Therefore, each individual instance will return the configured return values in the order specified, and then begin to repeat the last value.
Given a file named "multiple_calls_spec_with_allow_any_instance_of.rb" with:
class SomeClass
end
RSpec.describe "When the method is called multiple times on different instances with allow_any_instance_of" do
it "demonstrates the mocked behavior on each instance individually" do
allow_any_instance_of(SomeClass).to receive(:foo).and_return(1, 2, 3)
first = SomeClass.new
second = SomeClass.new
third = SomeClass.new
expect(first.foo).to eq(1)
expect(second.foo).to eq(1)
expect(first.foo).to eq(2)
expect(second.foo).to eq(2)
expect(first.foo).to eq(3)
expect(first.foo).to eq(3) # repeats last value from here
expect(second.foo).to eq(3)
expect(second.foo).to eq(3) # repeats last value from here
expect(third.foo).to eq(1)
expect(third.foo).to eq(2)
expect(third.foo).to eq(3)
expect(third.foo).to eq(3) # repeats last value from here
end
end
When I run rspec multiple_calls_spec_with_allow_any_instance_of.rb
Then the examples should all pass.