Zhaohan Weng
Zhaohan Weng

Reputation: 418

rspec: how to test the ensure block after raised an error

Here's my begin..rescue..ensure block. I want to write some test cases that after error is raised, the final result {} will be returned. I am using rspec 3.3.

def external_call
  result = ExternalApi.call 
rescue => e
  # handle the error, and re-raise
  Handler.handle(e)
  raise
ensure
  result.presence || {}
end

I have wrote test case for the rescue part:

context 'when external api raise error' do
  it 'handles the error, and re-raise' do
    allow(ExternalApi).to receive(:call).and_raise(SomeError)
    expect(Handler).to receive(:handle).with(e)
    expect { subject.external_call }.to raise_error(SomeError)
  end
end

But I am not sure how to test the ensure part after the error is re-raised. Here's my attempt:

  it 'returns {} after error raised' do
    allow(ExternalApi).to receive(:call).and_raise(SomeError)
    result = subject.external_call
    expect(result).to eq({})
  end

In this case, the test case will fail in the subject.external_call line, since it will raise error there. I am not sure how to test this cases after the error is re-raised.

Upvotes: 1

Views: 3602

Answers (1)

Alexa Y
Alexa Y

Reputation: 1854

When using begin/rescue/ensure block with implicit returns, ruby will return the last method to be run in the rescue block as the return value, not the ensure. If the value from the ensure block needs to be returned, it will either have to be explicitly returned, or not included in an ensure but instead moved outside of the begin/rescue block.

Below is an example which shows the difference.

class TestClass

  def self.method1
    raise 'an error'
  rescue
    'rescue block'
  ensure
    'ensure block'
  end

  def self.method2
    raise 'an error'
  rescue
    'rescue block'
  ensure
    return 'ensure block'
  end

  def self.method3
    begin
      raise 'an error'
    rescue
      'rescue block'
    end
    'ensure equivalent block'
  end
end

RSpec.describe TestClass do
  it do
    # does not work, method1 returns 'rescue block'
    expect(TestClass.method1).to eql 'ensure block'
  end

  it do
    # does work, as method2 explicitly returns 'ensure block'
    expect(TestClass.method2).to eql 'ensure block'
  end

  it do
    # does work, as method3 uses 'ensure equivalent block' as the inferred return
    expect(TestClass.method3).to eql 'ensure equivalent block'
  end
end

Upvotes: 2

Related Questions