RSpec test Rollbar called in method where error is raised - ruby-on-rails

I'm able to test that Rollbar.warning has been called, but when it is called in a method that also raises an error, it fails because an error is raised, and it skips Rollbar.warning.
context 'unauthorized' do
before do
allow(Rollbar).to receive(:warning)
end
it 'sends a warning to rollbar' do
subject.send(:log_and_raise_error)
expect(Rollbar).to have_received(:warning)
end
end
Here's the method I'm testing:
def log_and_raise_error
Rollbar.warning(
"Not authorized error accessing resource #{ResourceID}"
)
raise NotAuthorized
end
But when I run specs, it fails with:
1) Authorization unauthorized sends a warning to rollbar
Failure/Error: subject.send(:log_and_raise_error)
NotAuthorized
Any ideas how I can get around that error raising and still test Rollbar?

You can expect the error or rescue it:
expect the error:
it 'sends a warning to rollbar' do
expect { subject.send(:log_and_raise_error) }.to raise_error NotAuthorized
expect(Rollbar).to have_received(:warning)
end
rescue error:
it 'sends a warning to rollbar' do
subject.send(:log_and_raise_error) rescue NotAuthorized
expect(Rollbar).to have_received(:warning)
end
or
it 'sends a warning to rollbar' do
begin
subject.send(:log_and_raise_error)
rescue NotAuthorized
# noop
end
expect(Rollbar).to have_received(:warning)
end

Related

RSpec expect something before expect raise_error

Imagine there's a method that rescues and does some logging.
def do_something
# do stuff
some_client.call(var1)
rescue StandardError => e
# log some stuff.
Rails.logger.error("#{self.class} - Var 1 is #{var1}.") if e.is_a?(MyError)
raise
end
Then in the RSpec, I'd like to
assert the error is raised.
it logs the error
before do
allow(Rails.logger).to receive(:error)
allow(some_client).to receive(:call).and_raise(MyError)
end
it "logs the error" do
subject
expect(Rails.logger).to have_received(:error).with(/some message with var1/)
end
it "raises MyError" do
expect { subject }.to raise_error(MyError)
end
expect { subject }.to raise_error(MyError) part is working as expected, but how should I assert the logging? With the example code above, RSpec will report the error on the raised error without asserting the logging.
Just put them both in the same it. Expect that it raises an error and logs it.
it "raises MyError and logs it" do
expect { subject }.to raise_error(MyError)
expect(Rails.logger).to have_received(:error).with(/some message with var1/)
end
Alternatively if you really want to check that it logs the error in a separate it you'll have to rescue the error. Otherwise your spec will fail (unhandled error)
it "logs the error" do
subject
rescue
ensure
expect(Rails.logger).to have_received(:error).with(/some message with var1/)
end

mocking active record invalid exception with Rspec

I'm mocking active record invalid exception in the rspec.
here is the method im facing problem with. image_processing_error checks for the errors of the image object.
def save_image(image)
begin
image.save!
{ message: I18n.t("messages.image_saved") , status: 200 }
rescue ActiveRecord::RecordInvalid
image_processing_error(image)
end
end
private
def image_processing_error(image = nil)
if image && image.errors.any? && image.errors.messages.any?
{ message: image.errors.messages.values[0][0] , status: 422 }
else
{ message: I18n.t("errors.invalid_request"), status: 422 }
end
end
And here is my rspec for the same
# frozen_string_literal: true
require "rails_helper"
RSpec.describe ImagesService do
describe ".save_image" do
context "save image throws error" do
let(:image) { double("image", { "errors": { "messages": { "name": ["is invalid", "must be implemented"]}}}) }
before do
allow(image).to receive(:save!).and_raise(ActiveRecord::RecordInvalid)
end
subject { described_class.save_image(image) }
it "raised the error" do
// I want to test the **ActiveRecord::RecordInvalid
// I places NoMethodError to just pass the test
expect { subject }.to raise_error NoMethodError
expect { subject }.to raise_error ActiveRecord::RecordInvalid
end
end
end
end
I'm getting below error when i read the image error. what is the proper double value i have to keep it to work.
Failures:
1) ImagesService.save_image save image throws error raised the error
Failure/Error: expect { subject }.to raise_error ActiveRecord::RecordInvalid
expected ActiveRecord::RecordInvalid, got #<NoMethodError: undefined method `messages' for {:messages=>{:name=>["is invalid", "must be implemented"]}}:Hash> with backtrace:
In this case, the reason why you are not getting an exception on the call to subject is because an exception is not raised by it. Since you have set a rescue block in the save_image, if an exception is thrown inside of it, the rescue block will be called and the exception is not going to be propagated to the caller instead image_processing_error is going to be called.
So in your spec, when you call subject you are not going to get an exception but instead, you are going to get the parsed errors from image_processing_error which as I understand is actually the expected behavior.
In this case, what you could do is to test that the result you are getting from subject match with the expected error message that you would get in this case, something like:
it "returns the error messages" do
expect(response.body).to include("is invalid", "must be implemented")
end

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

Testing for Raised Errors

So I have this code:
begin
#location = Location.find(params[:id])
rescue ActiveRecord::RecordNotFound
puts 'ERROR MUST HAVE BEEN RAISED IF I APPEAR'
render action: :new, status: 404
end
and it's tested here:
let(:invalid_request){ get :show, id: Location.count + 1 }
describe 'runtime' do
before { invalid_request }
it 'should raise error ActiveRecord::RecordNotFound' do
expect { invalid_request }.to raise_error ActiveRecord::RecordNotFound
end
end
and yet when I run this expectation, I get this paradoxical output:
runtime
ERROR MUST HAVE BEEN RAISED IF I APPEAR
should raise error ActiveRecord::RecordNotFound (FAILED - 1)
Failures:
1) LocationsController#show when location doesn't exist runtime should raise error ActiveRecord::RecordNotFound
Failure/Error: expect { invalid_request }.to raise_error ActiveRecord::RecordNotFound
expected ActiveRecord::RecordNotFound but nothing was raised
# ./spec/controllers/locations_controller_spec.rb:45:in `block (5 levels) in <top (required)>'
So why is this? Rails raises the error automatically, so even though I'm not raising it manually, it's still being raised and should be testable...
Your code doesnt raise the error:
it is raised
but it is rescued
In the end, no error.
You should test that you get a 404.
Btw, render new with 404 feels wrong, you'd rather redirect.

How to write down the rspec to test rescue block.?

I have method like this
def className
def method_name
some code
rescue
some code and error message
end
end
So, How to write down the rspec to test rescue block..?
If you want to rescue, it means you expect some code to raise some kind of exception.
You can use RSpec stubs to fake the implementation and force an error. Assuming the execution block contains a method that may raise
def method_name
other_method_that_may_raise
rescue => e
"ERROR: #{e.message}"
end
hook the stub to that method in your specs
it " ... " do
subject.stub(:other_method_that_may_raise) { raise "boom" }
expect { subject.method_name }.to_not raise_error
end
You can also check the rescue handler by testing the result
it " ... " do
subject.stub(:other_method_that_may_raise) { raise "boom" }
expect(subject.method_name).to eq("ERROR: boom")
end
Needless to say, you should raise an error that it's likely to be raised by the real implementation instead of a generic error
{ raise FooError, "boom" }
and rescue only that Error, assuming this is relevant.
As a side note, in Ruby you define a class with:
class ClassName
not
def className
as in your example.
you can stub with return error
for example you have class with method like this :
class Email
def self.send_email
# send email
rescue
'Error sent email'
end
end
so rspec for raising error is
context 'when error occures' do
it 'should return error message' do
allow(Email).to receive(:send_email) { err }
expect(Email.send_email).to eq 'Error sent email brand'
end
end

Resources