Rspec: How to mute internal exception? - ruby-on-rails

I want to check some internal behaviour of method #abc which also raises an error.
def abc
String.class
raise StandardError
end
describe '#abc' do
it 'should call String.class' do
String.should_receive(:class)
end
end
String.class - is just an example of any method call of any class which I want to perform inside this method.
But I got an error:
Failure/Error: #abc
StandardError
How I can mute this exception so this spec would pass?

You cannot "mute" the exception; you can only catch it, either explicitly through a rescue clause or implicitly through an expectation. Note that this is different than substituting a test double for a called method. The exception is still getting raised by the code under test.
If you don't care whether an error is raised, then you can use:
abc rescue nil
to invoke the method. (Note: This will implicitly only catch StandardError)
If you want to using the should or expect syntax, you need to place the code which is going to raise the error within a block, as in the following:
expect {abc}.to raise_error(StandardError)
Combining this with the setting of an expectation that String.class be invoked, you get:
describe '#abc' do
it 'should call String.class' do
expect(String).to receive(:class)
expect {abc}.to raise_error(StandardError)
end
end

Related

How to check if the inside method has been called using RSpec

I got a method to update the person by id:
def update_person(id)
handle_exceptions do
person = Person.find(id)
#...other
end
end
When this id doesn't exist, the handle_exception should be called. But how could I test it? The test I wrote is:
context 'not found the proposals' do
subject {controller.send(:update_person, 3)}
before do
allow(Person).to receive(:find).and_raise(ActiveRecord::RecordNotFound)
allow(subject).to receive(:handle_exceptions)
end
it 'calls handle_exceptions' do
expect(subject).to have_received(:handle_exceptions)
end
end
But it not works, I got a failure said:
Failure/Error: expect(subject).to have_received(:handle_exceptions)
({:message=>"Not Found", :status=>:not_found}).handle_exceptions(*(any args))
expected: 1 time with any arguments
received: 0 times with any arguments
The handle_exceptions method is
def handle_exceptions
yield
rescue ActiveRecord::RecordNotFound => e
flash[:warning] = 'no record found'
Rails.logger.error message: e.message, exception: e
#error_data = { message: 'no record found', status: :not_found }
end
The problem is that you are calling the method under test in the subject block.
subject {controller.send(:update_person, 3)}
This is actually called before the example runs and before the before block.
context 'not found the proposals' do
before do
allow(subject).to receive(:handle_exceptions)
end
it 'calls handle_exceptions' do
controller.send(:update_person, "NOT A VALID ID")
expect(subject).to have_received(:handle_exceptions)
end
end
But as far as tests go this one is not good. You're testing the implementation of update_person and not the actual behavior. And you're calling the method with update_person.send(:update_person, 3) presumably to test a private method.
You should instead test that your controller returns a 404 response code when try to update with an invalid id. Also why you insist on stubbing Person.find is a mystery since you can trigger the exception by just passing an invalid id. Only stub when you actually have to.
After couple days working, I realized the reason I'm confused about it is I didn't figure out about 'who called this function', and I think it's the most important thing to know before test it. For the method like this:
class User::Controller
def methodA
methodB
end
def methodB
// ...
end
The mistake that I made is I thought the methodB is called by methods, but it's not. It's called by the controller, and that's the reason that I can't make the test works. There's so many things need to learn, and I hope there's one day that I won't have a mistake like this and be able to help others.

How to assert that a Rails method will not throw a NoMethodError

In my Rails application I am doing a unit test where I expect an error when calling a certain method.
test "sending a logout request while not logged should not return an error" do
assert_nothing_raised do
do_a_logout
end
end
Problem is that the test keeps reporting as Error instead as of Failure when do_a_logout errors out.
The error is NoMethodError: NoMethodError: undefined method 'forget' for nil:NilClass
It comes from this method two or three levels below the do_a_logout call
def forget user
user.forget
cookies.delete :user_id
cookies.delete :remember_token
end
Here, the error I am expecting is for user to be nil and fail when calling forget. How can I make the test report a Failure instead of an Error?
You want to use assert_raise with the exception class you're expecting:
assert_raise(NoMethodError) { do_a_logout }
ETA: If you want to test that no error was raised, you can use assert_nothing_raised:
assert_nothing_raised { do_a_logout }
assert_nothing_raised(NoMethodError) { do_a_logout }
However as pointed out on SO and elsewhere, there is no good reason to use this assertion, because its redundant: you never expect an error to be raised, unless you specify otherwise.
Moreover, it gives a less helpful failure than simply running the spec without the assertion. In particular, running with assert_nothing_raised when an error occurs will give you a backtrace only to the assert_nothing_raised call, while running the spec without this assertion will give a backtrace to the actual error that's occurring.
based on #nus comment,
test "sending a logout request while not logged should not return an error" do
do_a_logout
# assert true # optional
end
or
assert_silent { do_a_logout }

Rspec: Custom failure message for raise_error test

I'd like to use customised failure messages when testing if an error was raised or not while visiting a page with capybara.
My code:
expect(visit '/page/path').to_not raise_error(ActionView::Template::Error), "my custom error message"
I have also tried to use the lambda syntax (lambda { "my custom error message" }) with no success.
The problem seems to be that the error itself gets precedence for display over my custom error message. I have no interest in the error message - if one is raised, I know what the problem is, and the custom error message gives the solution (it's a long story involving VCR).
Right now I have a sketchy begin/rescue that looks like this:
begin
expect(visit '/page/path').to_not raise_error(ActionView::Template::Error)
rescue ActionView::Template::Error => error
expect(1).to eq(0), "my custom error message"
end
But, as you can see, I have to write a fake, always failing test, expect(1).to eq(0) which seems silly.
Is there any way to force my message to be displayed from the inline custom error message?
First of all you'll need to pass the block you want to execute to the expect method in order for the raise_error matcher to catch the errors raised in the block. Perhaps surprisingly the following fails for this reason:
describe 'Foo' do
it 'raises an error' do
expect(fail "wat").to raise_error(RuntimeError)
end
end
But if we re-write that expectation as follows it passes:
expect{fail "wat"}.to raise_error(RuntimeError)
As Rob mentioned though if you want to specify that an error is not raised you'll need to write your expectation as follows:
expect{fail "wat"}.to_not raise_error
and using that format you can specify a custom error message:
expect{fail "wat"}.to_not raise_error, "custom error message"
The reason why your second block of code seems to work is your visit '/page/path' raises an exception, then your exception handler catches it. It would be equivalent to the following:
begin
visit '/page/path'
rescue ActionView::Template::Error => error
expect(1).to eq(0), "my custom error message"
end
I have the following working locally:
expect { visit '/page/path' }.to_not raise_error, "Nope"
Specifying the class of exception you expect to_not have raised is apparently deprecated, according to RSpec's yelling at the top of the test results:
DEPRECATION: `expect { }.not_to raise_error(SpecificErrorClass)` is
deprecated. Use `expect { }.not_to raise_error` (with no args) instead.

Stubbing an exception, but evaluating normally (RSpec)

I'm trying to update an instance variable #status on an object based on the performance of a block. This block also makes calls to another class.
def run
#entries.keep_if { |i| valid_entry?(i) }.each do |e|
begin
unique_id = get_uniqueid e
cdr_record = Cdr.find_by_uniqueid(unique_id).first
recording = cdr_record.nil? ? NullAsteriskRecording.new : AsteriskRecording.new(cdr_record, e)
recording.set_attributes
recording.import
rescue Exception => e
fail_status
end
end
end
fail_status is a private method that updates the instance variable to :failed. Through breaking some other things, I've basically verified this code works, but I want a test in place as well. Currently, I've got the following:
context "in which an exception is thrown" do
before do
#recording = double("asterisk_recording")
#recording.stub(:import).and_raise("error")
end
it "should set #status to :failed" do
# pending "Update instance variable in rescue block(s) of #run"
subject.run
subject.status.should eq :failed
end
end
But the test always fails. The rescue block is never evaluated (I checked with a puts statement that would be evaluated when I hardcoded in a raise statement). Am I using the double feature wrong, here? Or am I doing myself in by stubbing out an exception, so the rescue block never gets run?
You set up #recording in your before block, but the code you have posted for your run method will not use that #recording instance and therefore the call to recording.import in the run method will not raise an exception.
In your run method, recording can either end up being an instance of NullAsteriskRecording or AsteriskRecording. If you know that it is going to be an AsteriskRecording as your current test implies, one approach would be to change your before block to the following:
before do
AsteriskRecording.any_instance.stub(:import).and_raise("error")
end

How to rescue the error exception raised by the `constantize` method?

I am using Ruby on Rails 3.2.2 and I would like to properly rescue the following process flow by raising a "custom" error message:
def rescue_method
# sample_string.class
# => String
# sample_string.inspect
# => "ARubyConstantThatDoesNotExist"
begin
build_constant(sample_string)
rescue
raise("My custom error message: #{build_constant(sample_string)} doesn't exist.")
end
end
def build_constant(sample_string)
"AModule::#{sample_string}".constantize
end
Note: I feel "forced" to use the constantize method also in the raised "custom" message in order to DRY code...
When the rescue_method is executed it seems that the raise("My custom error message") code is never executed and I get the following error:
uninitialized constant AModule::ARubyConstantThatDoesNotExist
How to properly display the raised "custom" message (since a further error exception is raised in the subsequent raised "custom" message)? What do you advice about?
The problem is that your build_constant method is doing two things:
Building the class name.
Turning the name into a class using constantize.
One of those things wants to use the other when an exception is raised. A simple solution is to pull those separate tasks apart:
def build_class_name(sample_string)
"AModule::#{sample_string}"
end
def rescue_method
name = build_class_name(...)
name.constantize
rescue NameError
raise("My custom error message: #{name} doesn't exist.")
end
You should also be more specific about the exception you're looking for so I added that for free.
If you don't want to rely on catching any exception, you could use safe_constantize (https://apidock.com/rails/ActiveSupport/Inflector/safe_constantize).
Same purpose as constantize but will return nil instead, whenever a Module does not exist.
'UnknownModule::Foo::Bar'.safe_constantize # => nil
begin
"AModule::#{sample_string}".constantize
rescue SyntaxError, NameError => err
raise("My custom error message")
end

Resources