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
Related
I am writing a test which should verify that when the object creation saves, indeed the exception is caught.
begin
object.create!(item)
rescue => exception
exception.message
end
rspec
expect{ exception.message }.to eq("Validation failed: Item name can't be blank")
output I got:
Failure/Error: expect{ exception.message }.to eq("Validation failed: Item name can't be blank")
You must pass an argument rather than a block to `expect` to use the provided matcher (eq "Validation failed: Item name can't be blank"), or the matcher must implement `supports_block_expectations?`.
Can anyone help me out?
You cannot test your code like that because the exception variable only exists in the rescue block. Instead, you need to call the method you want to test and add an expectation of what it should return.
Your example actually doesn't have such a method that could be called. I imagine that your method actually looks like this:
def do_stuff_with(object, item)
begin
object.create!(item)
rescue => exception
exception.message
end
end
Then you should be able to have an expectation like this:
expect(do_stuff_with(object, item)).to eq(
"Validation failed: Item name can't be blank"
)
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
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 make this test pass:
Here is an example of some rspec code,
class User
attr_accessor :count
def initialize
#count = 0
end
# sometimes raises
def danger
puts "IO can be dangerous..."
rescue IOError => e
#count += 1
end
#always raises
def danger!
raise IOError.new
rescue IOError => e
#count += 1
end
end
describe User do
describe "#danger!" do
it "its rescue block always increases the counter by one" do
allow(subject).to receive(:'danger!')
expect {
subject.danger!
}.to change(subject, :count).by(1)
end
end
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow(subject).to receive(:danger).and_raise(IOError)
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
end
I've also created a fiddle with these tests in it, so you can just make them pass. Please help me test the rescue block of a method!
Background:
My original question went something like this:
I have a method, like the following:
def publish!(resource)
published_resource = resource.publish!(current_project)
resource.update(published: true)
if resource.has_comments?
content = render_to_string partial: "#{ resource.class.name.tableize }/comment", locals: { comment: resource.comment_content_attributes }
resource.publish_comments!(current_project, published_resource.id, content)
end
true
rescue Bcx::ResponseError => e
resource.errors.add(:base, e.errors)
raise e
end
And I want to test that resource.errors.add(:base, e.errors) is, in fact, adding an error to the resource. More generally, I want to test the rescue block in a method.
So I'd like to write code like,
it "collects errors" do
expect{
subject.publish!(training_event.basecamp_calendar_event)
}.to change(training_event.errors.messages, :count).by(1)
end
Of course, this raises an error because I am re-raising in the rescue block.
I've seen a few answers that use the old something.stub(:method_name).and_raise(SomeException), but rspec complains that this syntax is deprecated. I would like to use Rspec Mocks 3.3 and the allow syntax, but I'm having a hard time.
allow(something).to receive(:method_name).and_raise(SomeException)
would be the new allow syntax. Check out the docs for reference.
I was misunderstanding what the allow syntax is actually for. So to make my example specs pass, I needed to do this:
describe "#danger" do
context "when it rescues an exception" do
it "should increase the counter" do
allow($stdout).to receive(:puts).and_raise(IOError) # <----- here
expect {
subject.danger
}.to change(subject, :count).by(1)
end
end
end
This thing that I'm stubing is not the method, or the subject, but the object that might raise. In this case I stub $stdout so that puts will raise.
Here is another fiddle in which the specs are passing.
Hey,
I am trying to use Mocha and Rspec to test a scenario where a method always raises some exception.
Here is the controller code I am trying to test:
def add_parent
begin
parent = Entity.find_by_id(params[:parent_id])
if !parent.nil?
#entity.add_parent!(parent)
flash[:success] = "Entity successfully updated."
else
raise "Parent does not exist."
end
rescue
flash[:error] = "Something bad happened. #{$!}"
end
redirect_to #entity
end
Here is the test code:
it "should flash error if exception is thrown when adding parent" do
Entity.any_instance.stubs(:add_parent!).raises(Exception)
lambda do
post :add_parent, :id => #entity[:id],
:parent_id => #parent_entity[:id]
end.should_not change(#entity.parents, :count)
flash[:error].should =~ /something bad happened/i
end
Here is the method which is being stubbed:
def add_parent!(parent)
Entity.transaction do
lock!('lock in share mode')
self.parents << parent
end
end
I am getting the following rspec error, which is pretty uninformative so I don't know how to resolve it..
Failures:
1) EntitiesController POST 'add_parent' for signed-in users allow access with edit permission should flash error if exception is thrown when adding parent
Failure/Error: post :add_parent, :id => #entity[:id],
Exception
# ./app/controllers/entities_controller.rb:81:in `add_parent'
# ./spec/controllers/entities_controller_spec.rb:1010
Wooah, first of all, it's a very bad habit to rescue from a big chunk of code without give the exception class you are expecting here. rescueing everything is no good. Best remove the whole rescueing, maybe use find over find_by_id so rails can catch that 404 error and you aren't bothered about. On the other hand, is this error supposed to happen a lot? Looks like some db-stuff going on so I wouldn't expect it to fail.
Secondly, I think you must test like the manual says about raise_error
http://relishapp.com/rspec/rspec-expectations/v/2-6/dir/built-in-matchers/raise-error-matcher
rescue with no argument will rescue StandardError and its descendants. If you want to catch ALL exceptions, you should rescue Exception as all exceptions descend from class Exception.
Source: http://www.ruby-forum.com/topic/44495