Reputation: 6586
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
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
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