Search code examples
rubyunit-testingrspecrspec-mocks

Verifying rspec mocks before the test is over


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

Solution

  • 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