Skip to main content

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.