RSpec retry throw exception and then return value - ruby-on-rails

I have a retry block
def my_method
app_instances = []
attempts = 0
begin
app_instances = fetch_and_rescan_app_instances(page_n, policy_id, policy_cpath)
rescue Exception
attempts += 1
retry unless attempts > 2
raise Exception
end
page_n += 1
end
where fetch_and_rescan_app_instances access the network so can throw an exception.
I want to write an rspec test that it throws an exception first time and doesn't throw an exception second time it gets called, so I can test if the second time it doesn't throw an exception, the my_method won't throw an exeption.
I know i can do stub(:fetch_and_rescan_app_instances).and_return(1,3) and first time it returns 1 and second time 3, but I don't know how to do throw an exception first time and return something second time.

You can calculate the return value in a block:
describe "my_method" do
before do
my_instance = ...
#times_called = 0
my_instance.stub(:fetch_and_rescan_app_instances).and_return do
#times_called += 1
raise Exception if #times_called == 1
end
end
it "raises exception first time method is called" do
my_instance.my_method().should raise_exception
end
it "does not raise an exception the second time method is called" do
begin
my_instance.my_method()
rescue Exception
end
my_instance.my_method().should_not raise_exception
end
end
Note that you should really not be rescuing from Exception, use something more specific. See: Why is it a bad style to `rescue Exception => e` in Ruby?

What you do is constrain the times the message should be received (receive counts), i.e. in your case you can
instance.stub(:fetch_and_rescan_app_instances).once.and_raise(RuntimeError, 'fail')
instance.stub(:fetch_and_rescan_app_instances).once.and_return('some return value')
Calling instance.fetch_and_rescan_app_instances first time will raise RuntimeError, and second time will return 'some return value'.
PS. Calling more than that will result in an error, you might consider using different receive count specification https://www.relishapp.com/rspec/rspec-mocks/docs/message-expectations/receive-counts

This has changed a little in RSpec3.x. It seems the best approach is to pass a block to the receive that defines this type of behaviour.
The following is from the docs suggesting how to create this type of transit failure:
(This errors every other time it is called... But is easy to adapt.)
RSpec.describe "An HTTP API client" do
it "can simulate transient network failures" do
client = double("MyHTTPClient")
call_count = 0
allow(client).to receive(:fetch_data) do
call_count += 1
call_count.odd? ? raise("timeout") : { :count => 15 }
end
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
expect { client.fetch_data }.to raise_error("timeout")
expect(client.fetch_data).to eq(:count => 15)
end
end

Related

How to allow only once rspec

i have a class called PeopleData.
i want to mock call on PeopleData to raise error. so i did this
allow(PeopleData).to receive(:fire_api).with(anything).and_raise(StandardError, "Error Here")
There are two classes call this PeopleData. First is TrafficData, and second is ClimateData.
TrafficData call PeopleData to get some data. I want to test rescue block in TrafficData when there is error on PeopleData call. So i did this
allow(PeopleData).to receive(:fire_api).with(anything).and_raise(StandardError, "Error Here")
expect(TrafficData.call).not_to raise_error
and it is fine. because it was expected. the rescue block is called and return something.
ClimateData class call using parameter data from TrafficData and it also call PeopleData. i want to test ClimateData call and return something without rescue anything.
But the problem is because i allow PeopleData to raise error, it will raise error also on ClimateData
This is what i did
allow(PeopleData).to receive(:fire_api).with(anything).and_raise(StandardError, "Error Here")
expect(TrafficData.call).not_to raise_error
expect do
ClimateData.check_climate!
end.to return(data)
it show error like this
raise StandardError, check_status.to_s
on the line in check_climate method definition.
Question: How to allow PeopleData mocking call ONLY ONCE? only in expect(TrafficData.call).not_to raise_error
so when i call ClimateData.check_climate! it will not raise error
you can do something like this:
values = [proc { raise 'Error Here' }]
allow(PeopleData).to receive(:fire_api).and_wrap_original do |original, *args|
values.empty? ? original.call(*args) : values.shift.call
end

Exception not falling in models

I wrote a function in ruby on rails model like below
def sorted_exp
begin
exp.order("currently_active = true DESC, TO_DATE(to_date, 'MM-DD-YYYY') DESC")
rescue
exp.order("currently_active = true DESC")
end
end
but there are few entries in to_date column due to which exception falls like 'september 2018'. When I tried to handle exception in model, it failed, and does not go in the rescue section. I don't know why it does not catch the error in model here, and why it does not return a query in the rescue section.
The exception raised is the following:
PG::InvalidDatetimeFormat: ERROR: invalid value "se" for "MM"
In the sorted_exp method, the output of the query is not being used. Rails actually executes the call to the DB when the value of the call is being used. In this case, the value of this is probably being used in some other function and the error is being raised from there, pointing to this line: exp.order("currently_active = true DESC, TO_DATE(to_date, 'MM-DD-YYYY') DESC")
I'm not sure of your exact use case, but the only way of catching the exception in this block would be to use values that the query is supposed to return, like counting the number of objects returned(Again, it depends on your use case).
For example, the following query raises an error inspite of being in a begin..rescue block:
begin
User.order("TO_DATE(users.created_at, 'MONTH-YYYY') DESC")
rescue
puts "In rescue block"
end
This raises the following error:
ActiveRecord::StatementInvalid: PG::UndefinedFunction: ERROR: function to_date(timestamp without time zone, unknown) does not exist
However, when the output of this query is used in the begin block itself, the exception gets caught. For example:
begin
sorted_users = User.order("TO_DATE(users.created_at, 'MONTH-YYYY') DESC")
count = sorted_users.count
rescue
puts "In rescue block"
end
The output for this is:
In rescue block
This is because the query was actually executed in the begin block itself, and hence, got caught by our rescue block.

Ruby: How to test private method?

I have implemented Optimistic Locking for Race Condition. If lock_version doesn't match with the updated lock_version in the database, then it will trigger retry three times. Can you suggest how to test this retry event.
Note: I can't change private method2
Public
def method1
begin
method2
rescue Exception => e
end
end
Private
def method2
tries = 0
begin
raise Product::StaleObjectError.new("Product is changed while you were editing") if stale_object?
// Do some work
raise Exception.new("Total amount used is greater than approved") if total_approved < 0
// Save Product
rescue Product::StaleObjectError => e
if tries < MAX_RETRIES
tries += 1
sleep(1 + tries)
reload
retry
else
raise Product::StaleObjectError("Product is changed while you were editing")
end
end
attributes
end
Test Case:
it 'method1 should call three times if there is stale_object' do
prod_v1 = Car.find(#car.id)
prod_v1.stub(:stale_object?).and_return true
prod_v1.method1
expect{prod_v1}.to receive(:method2).exactly(3).times
end
I am getting following error for test case
Failure/Error: expect{car_v1}.to receive(:method2).exactly(3).times
(#<Proc:).method2(any args)
expected: 2 times with any arguments
received: 0 times with any arguments
You are setting the expectation after the method was already called, that's why you get 0 times, it was called 0 times after that line.
Change the order, move the expectation ABOVE the line prod_v1.method1 and it will work. Using expect works for private and public methods.

Exception Handling in Ruby - call begin if it goes to rescue

I have a simple exception handling block
begin
<connect to network and make a request>
rescue
<comes here if it didnt connect / whatever other error>
end
I want to modify it such that if it comes to rescue - it goes to begin again. Give 5 tries . If still not connecting - come out of the block
you want the keyword retry. Couple that with a MAX_RETRIES. i.e. if (MAX_RETRIES -= 1) > 0
retry
Increment a counter and retry until that counter hits the value:
MAX_RETRIES = 5
retries = 0
begin
do_something
rescue ex
if retries += 1 <= MAX_RETRIES
retry
else
raise ex
end
end
You'll may want to retry on some Network related exceptions only, like timeout. Sometimes a not found or forbidden will always be, unless you poll until that changes.
This should do :
5.times do |i|
begin
1/0
rescue
puts 'Try #{i+1} failed ...'
next
end
puts 'Try #{i+1} success.'
break
end
write a method to connect to a network like this
def connect_to_server(retry_count)
begin
retry_count += 1
<connect to network and make a request>
rescue
if(retry_count <= 5)
connect_to_server(retry_count)
end
end
end
Call that method
connect_to_server(0)

Continue assertion after failures in ruby

My assertion example is below,
class test < Test::Unit::TestCase
def test_users
begin
assert_equal(user.name, 'John')
assert_equal(user.age, 30)
assert_equal(user.zipcode, 500002)
rescue Exception
raise
end
end
end
If any one of assertions fails, i should move on to process the next one and collect the failure assertions and show failures the end of the result.
I have used add_failure method, its working for looping condition
rescue Test::Unit::AssertionFailedError => e
add_failure(e.message, e.backtrace)
Can any one help ?
A good unit test should test exactly one thing, specifically to avoid problems like you just face. This test case will report on all failed tests, and not just the first failed test:
class MyTest < Test::Unit::TestCase
def test_user_name
assert_equal(user.name, 'John')
end
def test_user_age
assert_equal(user.age, 30)
end
def test_user_zipcode
assert_equal(user.zipcode, 500002)
end
end
Your main problem is that assert_equal ends up calling assert (as shown below) and assert will raise an ArgumentException.
File test/unit/assertions.rb, line 144
def assert_equal(exp, act, msg = nil)
msg = message(msg) {
# omitted code to save space
}
assert(exp == act, msg)
end
File test/unit/assertions.rb, line 29
def assert(test, msg = UNASSIGNED)
case msg
when UNASSIGNED
msg = nil
when String, Proc
else
bt = caller.reject { |s| s.rindex(MINI_DIR, 0) }
raise ArgumentError, "assertion message must be String or Proc, but #{msg.class} was given.", bt
end
super
end
You could extend Test::Unit::Assertions and provide an assertion that does not raise ArgumentError, which is what is stopping continuation past the failed assertion.
See this question for advice on going that direction and adding in safe assertions.
Please find this code for Continue assertion after failures in Ruby :
def raise_and_rescue
begin
puts 'I am before the raise.'
raise 'An error has occured.'
puts 'I am after the raise.'
rescue
puts 'I am rescued.'
end
puts 'I am after the begin block.'
end
Output :
ruby p045handexcp.rb
I am before the raise.
I am rescued.
I am after the begin block.
Exit code: 0

Resources