Zack
Zack

Reputation: 6586

Have RSpec verify that a method receives a particular block

How to I use RSpec to verify that a method receives a particular block? Consider this simplified example:

class MyTest
  def self.apply_all_blocks(collection, target)
    collection.blocks.each do |block|
      target.use_block(&block)
    end
  end
end

I want a spec that verifies that target.use_block is called with each block returned by collection.blocks.

The following code does not work:

describe "MyTest" do
  describe ".apply_all_blocks" do
    it "applies each block in the collection" do
      target = double(Object)
      target.stub(:use_block)

      collection = double(Object)
      collection.stub(:blocks).and_return([:a, :b, :c])

      target.should_receive(:use_block).with(:a)
      target.should_receive(:use_block).with(:b)
      target.should_receive(:use_block).with(:c)

      MyTest.apply_all_blocks(collection, target)
    end
  end
end

(Also, use_block does not necessarily invoke the block, so it not sufficient to test that the block receives call. Similarly, I don't think target.should_receive(:use_block).and_yield will do what I want.)

Upvotes: 1

Views: 702

Answers (2)

Nathan
Nathan

Reputation: 8026

I know this is an old question, but the currently accepted answer is incorrect.

The correct way to verify a particular block is received is by passing a verification block to should_receive that specifically compares the received block to the block you expected to receive:

In RSpec 2.13 (current at the time of the original question):

a = lambda {}
target.should_receive(:use_block) do |&block|
  expect(block).to eq(a)
end

In RSpec 3.4 (current at time of writing this):

a = lambda {}
expect(target).to receive(:use_block) do |&block|
  expect(block).to eq(a)
end

Passing a block to with as was suggested in another answer is not adequate to verify that the block is actually received, because rspec uses that block to setup a return value, not to compare against the block the method actually receives.

See documentation on passing blocks to receive.

Also see recent discussion on RSpec mailing list.

Upvotes: 1

Jim Stewart
Jim Stewart

Reputation: 17323

If you create lambdas instead of symbols, it will work as you expect:

describe "MyTest" do
  describe ".apply_all_blocks" do
    let(:a) { lambda {} }
    let(:b) { lambda {} }
    let(:c) { lambda {} }
    it "applies each block in the collection" do
      target = double(Object)
      target.stub(:use_block)

      collection = double(Object)
      collection.stub(:blocks).and_return([a, b, c])

      target.should_receive(:use_block).with(&a)
      target.should_receive(:use_block).with(&b)
      target.should_receive(:use_block).with(&c)

      MyTest.apply_all_blocks(collection, target)
    end
  end
end

Note: I changed the class name from Test to MyTest so it'll actually run; Test is going to clash with the real Test class. I modified your question too, so that it's cut-and-paste runnable.

Upvotes: 3

Related Questions