12

Spec:

before do
  Logger.should_receive( :write ).with 'Log message 1'    
end

it 'works' do
  get '/'
end

Sinatra App:

get '/'
  Logger.write( 'Log message 1' )
  Logger.write( 'Log message 2' )
end

This spec fails because of 'Log message 2'. How to tell RSpec to ignore any other messages, and only test for the expected message?

B Seven
  • 44,484
  • 66
  • 240
  • 385

2 Answers2

9

You need to stub the method that will be receiving the message before the message expectation.

# RSpec < 3
Logger.stub(write: nil)

The stub method is deprecated in RSpec 3, instead use one of the following

# RSpec >= 3
allow(Logger).to receive(:write).and_return(nil)  # Most appropriate in this case
allow(Logger).to receive(:write) { nil }  # Prefer block when returning something of a dynamic nature, e.g. a calculation
allow(Logger).to receive_messages(write: nil)  # Prefer hash when stubbing multiple methods at once, e.g. receive_messages(info: nil, debug: nil)

A method stub is an instruction to an object (real or test double) to return a known value in response to a message.

In this case, tell the Logger object to return the value nil when it receives the write message (the first time).

So your before block should look like this

before do
  Logger.stub(write: nil)
  Logger.should_receive(:write).with('Log message 1')
end
Dennis
  • 56,821
  • 26
  • 143
  • 139
Ash Wilson
  • 22,820
  • 3
  • 34
  • 45
  • `Logger.stub(:write)` worked. Why is this necessary? – B Seven Dec 10 '13 at 20:18
  • 1
    `stub` allows an instance to receive a message without actually performing the method call. `should_receive` sets an expectation, which means that the spec should fail if the message is not received as expected, including argument matching. In this case, you want to expect the specific call and allow the general one, hence calling both methods. Effectively, the `stub` call is saying "and it's okay if `Logger` calls `write` with other arguments, too". – Ash Wilson Dec 10 '13 at 20:42
3

This question is a little old, but having found my way here I thought I would answer it assuming rspec-mocks v3.

Stubbing the object beforehand then asserting with have_received works well when you care that the object receives a certain message, not that it only receives the message.

The subtle difference being between receive(:...) and have_received(:...)

To follow on from the original question, and assuming it was rewritten in rspec-mocks v3, my solution would be:

allow(Logger).to receive(:write)
get '/'
expect(Logger).to have_received(:write).with('Log message 1')

Note it is important to place the assertion at the end as it inspects the stub state when called, not on completion as is customary.

gondalez
  • 1,225
  • 10
  • 13
  • I wonder how to do it with `have_received`...or if there is a special reason that `receive` works differently from `have_received`? – B Seven Mar 22 '18 at 15:20
  • In my mind `expect(Logger).to receive(:write).with('Log message 1')` asserts that Logger receives **only** that single message (or any others you may add). It is a subtle and not well documented difference. I'll try to find time to submit a PR to rspec-mocks to update their readme/docs. – gondalez Mar 23 '18 at 00:33
  • 1
    Yes, that seems to be the current behavior. There could be an `only` method that makes it explicit, and could be used with both `receive` and `have_received`. `expect(Logger).to receive(:write).with('Log message 1').only` – B Seven Mar 23 '18 at 14:26