It seems like the standard way to use rspec mocks in a test case is to do something like this:
class MyTest
def setup
super
::RSpec::Mocks.setup(self)
end
def teardown
super
begin
::RSpec::Mocks.verify
ensure
::RSpec::Mocks.teardown
end
end
test "something"
foo = MyFoo.new
expect(foo).to receive(:bar).and_return(42)
ret = SomeClass.call_bar(foo)
assert_equal(42, ret)
end
end
That works okay. But if SomeClass.call_bar
used the return of foo.bar
as the return, and something was wrong with the code such that foo.bar
was never called, then I only receive a failure due to the assert_equal(42, ret)
line. I don't see any error like:
RSpec::Mocks::MockExpectationError: (foo).bar
expected: 1 time
received: 0 times
If I remove the assert_equal(42, ret)
line, then I do get the rspec expectation error. But I want to verify both things, that foo.bar
was called and the final return was 42. It's more important to know that foo.bar
wasn't called since that's the source of the reason that 42 wasn't returned.
If I'm expecting something like: expect(foo).not_to receive(:bar)
, then I do get that expectation error right at the source of the call, not later during the teardown.
Now, I can do something like put ::RSpec::Mocks.verify
just before the call to assert_equal
, but this doesn't feel right. I'm also not sure if I should be cleaning up the mocks at this point or not.
Is there some syntax like:
test "something"
foo = MyFoo.new
ret = nil
expect(foo).to receive(:bar).and_return(42).during do
ret = SomeClass.call_bar(foo)
end
assert_equal(42, ret)
end
So that the verification happens immediately after the block passed to during
? Or maybe if you have multiple doubles, you could do something like:
expect(dbl1).to receive(:one)
expect(dbl2).to receive(:two)
expect(dbl3).to receive(:three)
verify(dbl1, dbl2, dbl3).during do
my_code
end
You're looking for rspec spies.
Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using
have_received
.
You create a partial double out of your foo
with allow(...).to receive
, then can assert reception of the the message:
test "something"
foo = MyFoo.new
allow(foo).to receive(:bar).and_return(42)
ret = SomeClass.call_bar(foo)
expect(foo).to have_received(:bar)
assert_equal(42, ret)
end