Logo: Relish

  1. Sign in

Project: RSpec Mocks 3.7

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 declaritive 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.

Scenarios
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

Last published 10 months ago by myronmarston.