Monitoring Specific Messages in RSpec

Check out the full Monitoring Messages in Rspec series: part 1, part 2, part 3

A common question I see asked in the #rspec IRC channel is:

How do I verify only a specific message is received?

The context of this question is usually a method that sends the same message multiple time, but with different arguments.

Take the following contrived example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def do_stuff(thing)
  thing.do :having_fun
  thing.do :drink_coffee
  thing.do :code
end

describe 'Coffee time!' do
  it 'drinks the coffee' do
    thing = double('Thing')

    expect(thing).to receive(:do).with(:drink_coffee)

    do_stuff thing
  end
end

Alas, it fails:

1
2
3
Double "Thing" received :do with unexpected arguments
         expected: (:drink_coffee)
              got: (:having_fun)

The solution is to add a generic stub (a catchall) for the desired message:

1
2
3
4
5
6
7
8
9
10
describe 'Coffee time!' do
  it 'drinks the coffee' do
    thing = double('Thing')

    allow(thing).to receive(:do)
    expect(thing).to receive(:do).with(:drink_coffee)

    do_stuff thing
  end
end

There is another solution which can also be used: as_null_object.

The issue with as_null_object is that it will happily hum along even for invalid and unexpected messages. The double stub pattern above is explicit in the test, matching the catchall only with the message expectation.

1
2
3
4
5
6
7
8
9
describe 'Coffee time!' do
  it 'drinks the coffee' do
    thing = double('Thing').as_null_object

    expect(thing).to receive(:do).with(:drink_coffee)

    do_stuff thing
  end
end

Stay tuned for RSpec3 when as_null_object doubles will be smart enough to only stub matching messages. Or check out one of the many plugins: rspec-fire, bogus, and spy.