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:

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.

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:

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:

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:

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:

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.

Tagged as: bdd, ruby, rspec, how to
Fork me on GitHub