How to test rescue logic when re-raising error afterwards Rails/Rspec?

1.3k Views Asked by At

I have the following code which rescues an exception, updates some status attributes on a model, and then re-raises the error to allow it to bubble up to the calling class.

foo.rb

class Foo
  ...
  ...

  def bar
    External::Service.new().bar # this raises ArgumentError (for example)
  rescue ArgumentError => e
    message = 'Foo: Wrong id passed`
    report.update!(status: 'error')
    raise e
  end
end

In my specs I want to test that it updates the status, and also re-raises the error. Re-raising the error is fine to test, however I can't figure out a way to test updating message attribute as rspec just says an error was raised.

foo_spec.rb

RSpec.describe Foo do
  describe '#bar' do
    subject(:call_bar) { described_class.new().bar }

    let(:report) { create(:report, status: 'generating') }

    before do
      allow(External::Service).to receive(:new)
        .and_raise ArgumentError
    end
  
    it 're-raises the error' do 
      expect { call_bar }.to raise_error(ArgumentError) # works fine
    end

    it 'updates status' do
      expect { call_bar }.to change(report, :status) # does not work as rspec just sees error
    end
  end
end

I've tried something along the lines of this as well but the issue is that the rescue in the spec is called instead of the one in the code being tested when the error is first raised. The spec will pass but no attributes will be updated so it's a false positive.

it 'updates status' do
  call_bar
rescue
  expect(report.reload.status).to eq 'error'
end
1

There are 1 best solutions below

0
EdemaRuh On

The following was the solution that worked best for me in the end, however I don't like having two expectations in the one it block so if there are any other solutions please let me know.

it 're-raises the error', :aggregate_failures do
  expect { call_bar }.to raise_error(ArgumentError)
  expect(report.reload.status).to eq('error')
end

Both expectations pass now, and the status is being properly updated (double checked by adding a binding.pry after the second expectation and checking the value).

The { call_bar }.to raise_error(ArgumentError) allows rails to call the method without actually raising the error, and the changes made persist while still inside the it block, so adding the second expectation here works.