Block implementation
When you pass a block, RSpec will use your block as the implementation of the method. Any arguments (or a block) provided by the caller will be yielded to your block implementation. This feature is extremely flexible, and supports many use cases that are not directly supported by the more declarative fluent interface.
You can pass a block to any of the fluent interface methods:
allow(dbl).to receive(:foo) { do_something }
allow(dbl).to receive(:foo).with("args") { do_something }
allow(dbl).to receive(:foo).once { do_something }
allow(dbl).to receive(:foo).ordered { do_something }
Some of the more common use cases for block implementations are shown below, but this is not an exhaustive list.
Use a block to specify a return value with a terser syntax
Given a file named "return_value_spec.rb" with:
RSpec.describe "Specifying a return value using a block" do
it "returns the block's return value" do
dbl = double
allow(dbl).to receive(:foo) { 14 }
expect(dbl.foo).to eq(14)
end
end
When I run rspec return_value_spec.rb
Then the examples should all pass.
Use a block to verify arguments
Given a file named "verify_arguments_spec.rb" with:
RSpec.describe "Verifying arguments using a block" do
it "fails when the arguments do not meet the expectations set in the block" do
dbl = double
allow(dbl).to receive(:foo) do |arg|
expect(arg).to eq("bar")
end
dbl.foo(nil)
end
end
When I run rspec verify_arguments_spec.rb
Then it should fail with:
Failure/Error: expect(arg).to eq("bar")
Use a block to perform a calculation
Given a file named "perform_calculation_spec.rb" with:
RSpec.describe "Performing a calculation using a block" do
it "returns the block's return value" do
loan = double("Loan", :amount => 100)
allow(loan).to receive(:required_payment_for_rate) do |rate|
loan.amount * rate
end
expect(loan.required_payment_for_rate(0.05)).to eq(5)
expect(loan.required_payment_for_rate(0.1)).to eq(10)
end
end
When I run rspec perform_calculation_spec.rb
Then the examples should all pass.
Yield to the caller's block
Given a file named "yield_to_caller_spec.rb" with:
RSpec.describe "When the caller passes a block" do
it "can be yielded to from your implementation block" do
dbl = double
allow(dbl).to receive(:foo) { |&block| block.call(14) }
expect { |probe| dbl.foo(&probe) }.to yield_with_args(14)
end
end
When I run rspec yield_to_caller_spec.rb
Then the examples should all pass.
Delegate to partial double's original implementation within the block
Given a file named "delegate_to_original_spec.rb" with:
class Calculator
def self.add(x, y)
x + y
end
end
RSpec.describe "When using a block implementation on a partial double" do
it "supports delegating to the original implementation" do
original_add = Calculator.method(:add)
allow(Calculator).to receive(:add) do |x, y|
original_add.call(x, y) * 2
end
expect(Calculator.add(2, 5)).to eq(14)
end
end
When I run rspec delegate_to_original_spec.rb
Then the examples should all pass.
Simulating a transient network failure
Given a file named "simulate_transient_network_failure_spec.rb" with:
RSpec.describe "An HTTP API client" do
it "can simulate transient network failures" do
client = double("MyHTTPClient")
call_count = 0
allow(client).to receive(:fetch_data) do
call_count += 1
call_count.odd? ? raise("timeout") : { :count => 15 }
end
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
end
end
When I run rspec simulate_transient_network_failure_spec.rb
Then the examples should all pass.