Reputation: 8339
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
Upvotes: 1
Views: 806
Reputation: 6649
I believe that what you need is to aggregate failures https://relishapp.com/rspec/rspec-expectations/v/3-8/docs/aggregating-failures
In a "normal" setup, any error aborts the test and no later assertions are checked.
Upvotes: 1
Reputation: 62688
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
Upvotes: 3
Reputation: 8339
I don't think there's any built-in way to do it, but if you add the following class:
class VerifyDuring
def initialize(test, objects)
@test = test
@objects = objects
end
def during
yield
ensure
begin
@objects.each do |object|
RSpec::Mocks.proxy_for(object).verify
end
rescue Exception => e
@test.flunk e
end
end
end
And the following method to your test class:
def verify(*objects)
VerifyDuring.new(self, objects)
end
You can do this:
verify(dbl1, dbl2, dbl3).during do
my_code
end
Upvotes: -2