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
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.
I have a method similar to this:
def create
reservation = Reservation.create(params[:reservation_params])
if reservation.valid?
reserved_hour = ReservedHour.create(params[:reserved_hour_params])
if reserved_hour.valid?
notification = Notification.create(params[:notification])
if !notification.valid?
reservation.destroy
reserved_hour.destroy
end
else
reservation.destroy
end
end
end
Now I'd like to test database fail cases with RSpec. For example I'd like to simulate database crash during notification creating and test if reservation and reserved_hour destroy successfully. Is there some way to do this without expanding my create method for test purposes only? I can simulate crash for all three cases by running ActiveRecord::Base.remove_connection, but I have no idea how could I test the case with a single crash.
Your code isn't going to work because all of your .create calls will always return something (either a saved record or an unsaved record) and your if statements will always be true.
Why not use .create! (which will raise an error if create is unsuccessful) within a transaction. Something like:
def create
ActiveRecord::Base.transaction do
begin
Reservation.create!(params[:reservation_params])
ReservedHour.create!(params[:reserved_hour_params])
Notification.create!(params[:notification])
rescue SomeError =>
# do something with SomeError
end
end
end
That way, your transactions will be rolled back if you have an error and you don't have to do all that .destroy business.
I'm trying to write a failing Rspec test. The actual test is associated with much longer code, but I narrowed down the problem to the class method it's testing.
Here's the test in Rspec:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
Stock.change_to_error
expect(cash.status).to eq('Available')
end
end
end
This is testing a model class method in Stock.rb:
def self.change_to_error
self.all.each do |stock|
stock.status = "Error"
stock.save
end
end
For some reason, this passes. However, if I changed it to use an instance method, it will fail like it should:
If stock_spec.rb changed to instance method:
context "For '.CASH.' as a stock" do
let!(:cash) { FactoryGirl.create(:stock, symbol: '.CASH.', name: 'cash', status: 'Available') }
describe "When update_stock runs on it" do
it "should still have an 'Available' status" do
# status should be 'Error' and test should fail
cash.change_to_error
expect(cash.status).to eq('Available')
end
end
end
And if stock.rb class method turned into an instance method:
def change_to_error
self.status = 'Error'
self.save
end
This would pass. Unfortunately, I have to use a class method instead of an instance method because I want to update all stocks in the DB. "Change_to_error" methods are just there to figure out the problem. Does anyone know why it passes as a class method when it should fail? But it fails correctly when it's using an instance method?
Effectively, what is happening is that the class method does not change the status attribute of 'cash', but the instance method does. I don't know why that is happening.
FYI, I'm using rspec-rails
Solution: Need to put 'cash.reload' after 'Stock.change_to_error' and before the expect line.
When using let! the object is created before the test. Updating the underlying data outside the object causes the instance to be outdated. Calling reload on it forces ActiveRecord to refresh it from the database.
When you use let, RSpec does not call the block until the first time you reference the attribute, in this case, cash. So in your first example, you're running change_to_error on no records at all and then checking the status on cash, a record that gets created on the line with expect. In your second example, the cash object is created, then changed to an error. I'd recommend tailing your log to confirm this (tail -f log/test.log)
If you change to let!, RSpec will create the object before every example is run. Another alternative is to reference cash in your example before calling change_to_error on all records that are created.
How can I use rescue to continue a loop. I’ll make an example
def self.execute
Foo.some_scope.each do |foo|
# This calls to an external API, and sometimes can raise an error if the account is not active
App::Client::Sync.new(foo).start!
end
end
So normally rescue Bar::Web::Api::Error => e would go at the end of the method and the loop will stop. If I could update a attribute of the foo that was rescued and call the method again, that foo would not be included in the scope and I would be able to start the loop again. But the issue with that is, I only want this once for each foo. So that way would loop through all of the existing foo again.
What’s another way I could do this? I could make a private method that is called at the top of the execute method. This could Loop through the foo and update the attribute so they aren’t part of the scope. But this sounds like an endless loop.
Does anyone have a good solution to this?
You can put a begin and rescue block within the loop. You talk about "updating an attribute of the foo" but it seems you only want that to ensure this foo is not processed on a restart of the loop, but you don't need to restart the loop.
def self.execute
Foo.some_scope.each do |foo|
# This calls to an external API, and sometimes can raise an error if the account is not active
begin
App::Client::Sync.new(foo).start!
rescue Bar::Web::Api::Error
foo.update(attribute: :new_value) # if you still need this
end
end
end
You could use retry. It will re-execute the whole begin block when called from a rescue block. If you only want it to retry a limited number of times you could use a counter. Something like:
def self.execute
Foo.some_scope.each do |foo|
num_tries = 0
begin
App::Client::Sync.new(foo).start!
rescue
num_tries += 1
retry if num_tries > 1
end
end
end
Documentation here.
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