Spec Your Yields in RSpec

Message expectations in RSpec’s Mocking/Stubing framework provide means for spec’ing the yielded objects of a method. For example, consider the following spec where we expect the here_i_am method to yield self:

1
2
3
4
5
6
7
8
9
10
11
12
describe Triviality do
describe '#here_i_am' do
let(:triviality) { Triviality.new }
it 'yields self' do
triviality.should_receive(:here_i_am).and_yield(triviality)
triviality.here_i_am { }
end
end
end

Nice and easy. First we set the expectation and then we exercise the method so that the expectation is met, passing it a “no op” block - {}.

Here’s the method to make it pass.

1
2
3
4
5
6
7
class Triviality
def here_i_am
yield self
end
end

Furthermore, we can test many yielded values by chaining the and_yield method on the expectation. Let’s add a spec for a method that yields many times and see how that would play out:

1
2
3
4
5
6
7
8
9
10
11
12
describe Triviality do
describe '#one_two_three' do
let(:triviality) { Triviality.new }
it 'yields the numbers 1, 2 and 3' do
triviality.should_receive(:one_two_three).and_yield(1).and_yield(2).and_yield(3)
triviality.one_two_three { }
end
end
end

And the method to make that pass:

1
2
3
4
5
6
7
8
9
class Triviality
def one_two_three
yield 1
yield 2
yield 3
end
end

This is kind of ugly though. What if it yields many more times, or if you just want to test that it yields all items of an array? A good example of this is the Enumerable’s each method. In such cases we can store the MessageExpectation object and call and_yield on it many times, in a loop. Take a look at the following example where we yield each letter of the alphabet:

1
2
3
4
5
6
7
8
9
10
11
12
13
describe Triviality do
describe '#alphabet' do
let(:triviality) { Triviality.new }
it 'yields all letters of the alphabet' do
expectation = triviality.should_receive(:alphabet)
('A'...'Z').each { |letter| expectation.and_yield(letter) }
triviality.alphabet { }
end
end
end

And finally, the method to make it pass:

1
2
3
4
5
6
7
class Triviality
def alphabet
('A'...'Z').each do { |letter| yield letter }
end
end

and_yield is not only useful for message expectations. You can also use it on your stubs, just like you’d use and_returns.