How to allow only once rspec - ruby-on-rails

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

Related

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.

How to DRY up Webhook handlers in Rails

I've been developing Stripe Webhook handler to create/update records depending the values.
It's not really hard, if it's a simple like this below;
StripeEvent.configure do |events|
events.subscribe 'charge.succeeded' do |event|
charge = event.data.object
StripeMailer.receipt(charge).deliver
StripeMailer.admin_charge_succeeded(charge).deliver
end
end
However If I need to store the data conditionally, it could be little messier.
In here I extracted the each Webhook handler and defined something like stripe_handlers/blahblah_handler.rb.
class InvoicePaymentFailed
def call(event)
invoice_obj = event.data.object
charge_obj = retrieve_charge_obj_of(invoice_obj)
invoice = Invoice.find_by(stripe_invoice_id: charge_obj[:invoice])
# common execution for subscription
invoice.account.subscription.renew_billing_period(start_at: invoice_obj[:period_start], end_at: invoice_obj[:period_end])
case invoice.state
when 'pending'
invoice.fail!(:processing,
amount_due: invoice[:amount_due],
error: {
code: charge_obj[:failure_code],
message: charge_obj[:failure_message]
})
when 'past_due'
invoice.failed_final_attempt!
end
invoice.next_attempt_at = Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i)
invoice.attempt_count = invoice_obj[:attempt_count].to_i
invoice.save
end
private
def retrieve_charge_obj_of(invoice)
charge_obj = Stripe::Charge.retrieve(id: invoice.charge)
return charge_obj
rescue Stripe::InvalidRequestError, Stripe::AuthenticationError, Stripe::APIConnectionError, Stripe::StripeError => e
logger.error e
logger.error e.backtrace.join("\n")
end
end
end
I just wonder how I can DRY up this Webhook handler.
Is there some best practice to approach this or any ideas?
I suggest re-raising the exception in retrieve_charge_obj_of, since you'll just get a nil reference exception later on, which is misleading. (As is, you might as well let the exception bubble up, and let a dedicated error handling system rescue, log, and return a meaningful 500 error.)
a. If you don't want to return a 500, then you have a bug b/c retrieve_charge_obj_of will return nil after the exception is rescued. And if charge_obj is nil, then this service will raise a NPE, resulting in a 500.
if invoice_obj[:next_payment_attempt] can be !present? (blank?), then what is Utils.unix_time_to_utc(invoice_obj[:next_payment_attempt].to_i) supposed to mean?
a. If it was nil, false, or '', #to_i returns 0 -- is that intended? ([]/{} is also blank? but would raise)
Conceptually, this handler needs to issue a state transition on an Invoice, so a chunk of this logic can go in the model instead:
class Invoice < ApplicationRecord
# this method is "internal" to your application, so incoming params should be already "clean"
def mark_payment_failed!(err_code, err_msg, attempt_count, next_payment_at)
transaction do # payment processing usually needs to be transactional
case self.state
when 'pending'
err = { code: err_code, message: err_msg }
self.fail!(:processing, amount_due: self.amount_due, error: err)
when 'past_due'
self.failed_final_attempt!
else
ex_msg = "some useful data #{state} #{err_code}"
raise InvalidStateTransition, ex_msg
end
self.next_attempt_at = next_payment_at
self.attempt_count = attempt_count
self.save
end
end
class InvalidStateTransition < StandardError; end
end
Note: I recommend a formal state machine implementation (e.g. state_machine) before states & transitions get out of hand.
Data extraction, validation, and conversion should happen in the handler (that's what "handlers" are for), and they should happen before flowing deeper in your application. Errors are best caught early and execution stopped early, before any action has been taken.
There are still some other edge cases that I see that aren't really handled.

How execute method after each action only when no one exception happens

I'm trying create one webservice that consumes yammer with rails, and I'm trying to return message that indicates when action was successfully executed.
around_action :sendOkMessage
rescue_from Exception do |exception|
yammerResult = YammerResult.new
yammerResult.status = 'NOK'
yammerResult.message = exception.inspect
render json: yammerResult
end
The problem is, around_action is always executed (even when error happens), and I don't want this.
What I want is:
No exception raised? Run sendOkMessage, return ok message
Exception raised? Catch exception, return nok message
We could use a flag and toggle it, that is define an instance variable #failed = true in the exception block. And make the first line of the :sendOkMessage. To check the #failed flag, if its true - toggle ito false and return.
def sendOkMessage
#failed = false and return unless #failed
#failed = false

RSpec 3 - raising an error on purpose to test it is rescued

Consider a simple method -
def my_method(users)
eligible_users = []
users.each do |u|
# Go to the next user unless they are eligible
next unless is_eligible?(u)
begin
update_user(u)
eligible_users << u
rescue
puts "Error occured"
# Prints some other stuff about error
next
end
end
end
A key feature of this method is that it loops through users but continues to the next user even if a given user throws an error.
If I were writing spec tests for this, I'd love to pass an array of 3 users and purposely have it error out on the first user. I can then check that the 2nd and 3rd were still correctly processed.
How would I go about raising an error on purpose for only one of the result sets?
I was thinking I could stub is_eligible? and return an error for one of the result and true for the remainder -
allow_any_instance_of(MyClass).to receive(:is_eligible?).and_return(
raise StandardError.new,
true,
true
)
As expected, that doesn't work. Any other approaches?
Thanks!
I can't exactly answer the question, but rather than doing a begin resuce thing,you can follow this approach,
Make the update_user return true or false.
Keep an array of users that falied to update.
return an object like
response: { status: "failure", message: "falied to update #{pluralize(failed_users_array.count, 'user')}", failures: failed_users_array.join(", ") } for failures and
response: { status: "success", message: "#{pluralize(users.count, 'user')} updated successfully" } for all success.
now you can easily test,
have two cases, one where you can test failures and when you can test all success.
Just stub the response object.
For raising errors, you have to do .and_raise("some tezt or StandardError.new") , thats in the docs.

RSpec retry throw exception and then return value

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

Resources