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
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.
Both expectations pass now, and the status is being properly updated (double checked by adding a
binding.pryafter 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.