My application is propagating a ActiveRecord::RecordNotFound to the controller when a model can't retrieve a record.
Here is the DB query:
def self.select_intro_verse
offset = rand(IntroVerse.count)
IntroVerse.select('line_one', 'line_two', 'line_three', 'line_four', 'line_five').offset(offset).first!.as_json
end
first! raises a ActiveRecord::RecordNotFound if no record is found, and I am able to rescue it and render an appropriate template in my controller.
This is the expected behavior so I would like to have a test for it in my specs. I wrote:
context "verses missing in database" do
it "raises an exception" do
expect(VerseSelector.select_intro_verse).to raise_exception
end
end
When I run rspec:
1) VerseSelector verses missing in database raises an exception
Failure/Error: expect(VerseSelector.select_intro_verse).to raise_exception
ActiveRecord::RecordNotFound:
ActiveRecord::RecordNotFound
To me the test fails even though the exception is raised! How can I make my test pass?
Look in documentation for rspec-expectations#expecting-errors this:
expect(VerseSelector.select_intro_verse).to raise_exception
should be except syntax for exception must be lambda or proc:
expect { VerseSelector.select_intro_verse }.to raise_exception(ActiveRecord::RecordNotFound)
Related
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.
My test looks like this:
it 'does return an error when passing a non-subscription or trial membership' do
expect(helper.description_for_subscription(recurring_plan)).to raise_error(RuntimeError)
end
My method returns this:
fail 'Unknown subscription model type!'
Yet Rspec comes back with this failure message:
Failure/Error: expect(helper.description_for_subscription(recurring_plan)).to raise_error(RuntimeError)
RuntimeError:
Unknown subscription model type!
What is going on??
You should wrap the expectation in a block, using {} instead of ():
expect{
helper.description_for_subscription(recurring_plan)
}.to raise_error(RuntimeError)
Check the Expecting Errors section here
I'm trying to test PG database constraints in a rails 4 using RSpec, and I'm not sure how to set it up.
My thought was to do something like this:
before do
#subscriber = Marketing::Subscriber.new(email: "subscriber#example.com")
end
describe "when email address is already taken" do
before do
subscriber_with_same_email = #subscriber.dup
subscriber_with_same_email.email = #subscriber.email.upcase
subscriber_with_same_email.save
end
it "should raise db error when validation is skipped" do
expect(#subscriber.save!(validate: false)).to raise_error
end
end
When I run this, it does in generate an error:
PG::UniqueViolation: ERROR: duplicate key value violates unique constraint
However, the test still fails.
Is there a proper syntax to get the test to pass?
Try
it "should raise db error when validation is skipped" do
expect { #subscriber.save!(validate: false) }.to raise_error
end
For more information, check the more info on rspec-expectations expect-error matchers
Hope this helps!
Slight modification to #strivedi183's answer:
it "should raise db error when validation is skipped" do
expect { #subscriber.save!(validate: false) }.to raise_error(ActiveRecord::RecordNotUnique)
end
The justification for being more verbose with the error class is that it protects you from having other possible checks that may raise an error that are not related to specific duplication error you wish to raise.
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
I had a really weird rspec case scenario. I tried to test if my function handles exception correctly. And the following is my code:
in User.rb:
def welcome_user
begin
send_welcome_mail(self)
rescue Exception => exception
ErrorMessage.add(exception, user_id: self.id)
end
end
end
in user_spec.rb
it "adds to error message if an exception is thrown" do
mock_user = User.new
mock_user.stub(:send_welcome_mail).and_raise(Exception)
ErrorMessage.should_receive(:add)
mock_user.welcome_user
end
The test passed, but when I change ErrorMessage.should_receive(:add) to ErrorMessage.should_not_receive(:add), It also passed, any insights?
Since rspec 2.11 exposed one of my tests to exhibit such "abnormality", I decided to raise the issue on github. You may following the discussion at https://github.com/rspec/rspec-mocks/issues/164
Summary: any_instance.should_not_receive is undefined, avoid
What you can try to use instead is .should_receive in combination with .never:
ErrorMessage.should_receive(:add).never