Testing for Raised Errors - ruby-on-rails

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.

Related

How to Raise Runtime Error in Rspec-Rails

I have to test a code where I am raising some errors, I tried several techniques but it failed. The structure of the class is defined below:
SchemaController:
class SchemasController < ApplicationController
def index
#get_schema = Api::AnalyticsQueryBuilderMetadataService::Schema.show
end
end
Show method under Api -> AnalyticsQueryBuilderMetadataService -> Schema.rb file:
def self.show
params = { 'limit' => 40 }
response = Api::Connection.initiate_request('entities', params)
if response.nil?
Rails.logger.error 'Data not found for ClientId '
raise 'Data not found'
else
get_schema(response)
end
end
Rspec test I wrote for schema_spec.rb:
require 'rails_helper'
require 'spec_helper'
RSpec.describe Api::AnalyticsQueryBuilderMetadataService::Schema do
describe 'GET all schema' do
before do
# allow_any_instance_of(SchemasController).to receive(:connection).and_return({})
#binding.pry
allow(Api::Connection).to receive(:initiate_request).and_return(nil)
end
context 'When no json body is passed' do
it 'Raises NoMethodError' do
# obj = SchemasController.new
result = Api::AnalyticsQueryBuilderMetadataService::Schema.show()
# expect {result}.to raise_error(RuntimeError)
expect{result}.to raise_error
end
end
end
end
But It is giving error as:
Failures:
1) Api::AnalyticsQueryBuilderMetadataService::Schema GET all schema When no json body is passed Raises NoMethodError
Failure/Error: raise 'Data not found'
RuntimeError:
Data not found
# ./app/lib/api/analytics_query_builder_metadata_service/schema.rb:22:in `show'
# ./spec/lib/api/analytics_query_builder_metadata_service/schema_spec.rb:17:in `block (4 levels) in <top (required)>'
Finished in 2.3 seconds (files took 5.63 seconds to load)
44 examples, 1 failure
Failed examples:
rspec ./spec/lib/api/analytics_query_builder_metadata_service/schema_spec.rb:15 # Api::AnalyticsQueryBuilderMetadataService::Schema GET all schema When no json body is passed Raises NoMethodError
Help me to solve this.
From the docs;
Use the raise_error matcher to specify that a block of code raises an
error.
It means that the code in the block should be the one raising the error, but in your case the error is being raised when you declare the result variable.
To make it work you can skip the variable declaration and pass the variable value as the expect block;
expect { Api::AnalyticsQueryBuilderMetadataService::Schema.show }
.to raise_error(StandardError, 'Data not found')

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 test Rollbar called in method where error is raised

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

undefined method `response_code' for nil:NilClass for IntegrationTest that fails

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

How to test exception raising in Rails/RSpec?

There is the following code:
def index
#car_types = car_brand.car_types
end
def car_brand
CarBrand.find(params[:car_brand_id])
rescue ActiveRecord::RecordNotFound
raise Errors::CarBrandNotFound.new
end
I want to test it through RSpec. My code is:
it 'raises CarBrandNotFound exception' do
get :index, car_brand_id: 0
expect(response).to raise_error(Errors::CarBrandNotFound)
end
CarBrand with id equaling 0 doesn't exist, therefore my controller code raises Errors::CarBrandNotFound, but my test code tells me that nothing was raised. How can I fix it? What do I wrong?
Use expect{} instead of expect().
Example:
it do
expect { response }.to raise_error(Errors::CarBrandNotFound)
end
In order to spec error handling, your expectations need to be set on a block; evaluating an object cannot raise an error.
So you want to do something like this:
expect {
get :index, car_brand_id: 0
}.to raise_error(Errors::CarBrandNotFound)
See Expect error for details.
I am a bit surprised that you don't get any exception bubbling up to your spec results, though.
get :index will never raise an exception - it will rather set response to be an 500 error some way as a real server would do.
Instead try:
it 'raises CarBrandNotFound exception' do
controller.params[:car_brand_id] = 0
expect{ controller.car_brand }.to raise_error(Errors::CarBrandNotFound)
end

Resources