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
Related
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
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
Help me. I don't understand how to fix it. I tried to many variations...
driver_iq_driver_spec.rb
describe '.perform' do
it 'correctly parse response' do
driver = described_class.new(dot_application, background_check_type, provider_setting).perform
expect(driver).to be_instance_of(BackgroundCheck)
expect(driver).to have_attributes(status: 'inprogress', background_check_type_id: 4)
end
context 'when exception when status is Error' do
before { allow_any_instance_of(described_class).to receive(:driver_iq_api).and_return('https://test/error') }
it 'returns error message' do
expect { described_class.new(dot_application, background_check_type, provider_setting).perform }.
to raise_error(RuntimeError)
end
end
end
Error: RSpec/AnyInstance: Avoid stubbing using allow_any_instance_of.
before { allow_any_instance_of(described_class).to receive(:driver_iq_api).and_return('https://test/error') }
You have a pretty specific instance of your described_class that you can stub:
context 'when exception when status is Error' do
let(:subject) do
described_class.new(dot_application, background_check_type, provider_setting)
end
before do
allow(subject).to receive(:driver_iq_api).and_return('https://test/error')
end
it 'returns error message' do
expect { subject.perform }.to raise_error(RuntimeError)
end
end
Assuming perform raises the error, not initializing the instance.
I have a not-null constraint on a column. I have an IntegrationTest test case that posts an instance without that column. My goal is to assert that response code returned is 400.
My first try was:
test "should fail if no context" do
post trips_url, params: { trip: { state: #trip.state } }
assert_response 400
end
But this fails with Minitest::UnexpectedError: ActiveRecord::NotNullViolation: SQLite3::ConstraintException: NOT NULL constraint failed: trips.context:....
Which I don't really understand. Isn't post something like an HTTP client, and so it shouldn't be affected by the exceptions thrown in the controller?
But anyway, next I tried:
test "should fail if no context" do
assert_raises(ActiveRecord::NotNullViolation) do
post trips_url, params: { trip: { state: #trip.state } }
end
assert_response 400
end
But this fails with Minitest::UnexpectedError: NoMethodError: undefined method `response_code' for nil:NilClass, as if response_code wasn't set since an exception is thrown.
So, my question: How to achieve my goal? When testing an endpoint, I don't really care about what exceptions were thrown; I care about what response it's returned. So, how to assert on the response in the case of a failure?
I'm using Rails 5.1.4.
Depends on if your controller is rescuing the ActiveRecord::NotNullViolation and rendering the 400 ? I imagine controller and spec to be something like below
Controller code
class User < ApplicationController
def create
.....
rescue ActiveRecord::NotNullViolation => e
render text: e.message, status: :bad_request
end
end
Spec code
it 'POST with null data returns bad request' do
post to/user/path
expect(response.code).to eq '400'
end
I am trying to create an RSpec test which detects if a request can crash the controller, usually a 500 error. So I want to be able to distinguish between:
nil.invalid_method # raises NoMethodError
from
params.require(:required_parameter) # raises ActionController::ParameterMissing
in a controller in a generic way. When I do a request,feature or controller test it raises an exception:
describe "Post", type: :request do
it 'does not crash when no params given' do
post '/posts' # this line launches an exception
expect(page).to_not have_http_status(500)
end
end
It seems that before RSpec (or Rails I don't know) had a different behaviour, similar to I'm looking for:
rails 4 api rspec test http status code 410
Rspec shows different status code than browser
How to use HTTP status code symbols in RSpec?
How can I do this? Or how would you do?
Thanks for your time.
You can use a controller spec that doesn't render a 500, but raises the exception instead:
describe "PostController", type: :controller do
describe "POST index" do
it 'does not crash with valid params' do
expect {
post :index, { post: { title: 'foo' } }
}.to_not raise_exception
end
end
describe "POST index" do
it 'crashes without params' do
expect {
post :index
}.to raise_exception(ActionController::ParameterMissing)
end
end
end
Also note the curly brackets { ... } after expect.
You can test that the controller does not raise an uncaught exception by using the raise_error matcher:
RSpec.describe "Things", type: :request do
describe "POST /things" do
it "does not raise an error" do
# we pass a block to expect
expect { post things_path }.to_not raise_error
end
end
end
If the exception is rescued in the controller by using the rescue keyword or Rails rescue_from you would test the response code as usual:
class ThingsController < ApplicationController
rescue_from ActionController::ParameterMissing do
head 500
end
def create
raise ActionController::ParameterMissing.new('foo')
end
end
RSpec.describe "Things", type: :request do
describe "POST /things" do
it "work even if the param is not provided" do
post things_path
expect(response).to successful
end
end
end
In this case it is much more useful to test that the response is what you expected it to be - not that it is not a 500.