RSpec Stubbing Service Object Method - ruby-on-rails

I'm having an issue with stubbing out a call to a service object (QuickbooksService) from an AR object. As far as I can tell the method should be stubbed properly and return the value I'm specifying but when I run the spec I can see the method being called and failing.
Here's the class definition of the object I'm testing.
class Order < ActiveRecord::Base
def create_invoice
QuickbooksService.new(:estimate).create_invoice(arg1, arg2, arg3)
end
end
And from order_spec
describe("#create_invoice") do
expect(QuickbooksService.new(:estimate)).to receive(:create_invoice).and_return(1)
end
I've also tried
allow(QuickbooksService.new(:estimate)).to receive(:form_invoice_create).with(anything()).and_return(1)
So instead of returning 1 the create_invoice method is being executed inside of QuickbooksService. Any insight would be appreciated!

The problem you are having is that you are stubbing a seperate instance. i.e When you define the expectation, you telling it to expect that a particular instance receives a call to the method, but when the code is executed, it is creating a different instance. What you need to do is allow any instance to receive the method call. Something like allow_any_instance_of(QuickbooksService).to receive(:invoice_create) will work, but it is much better practice to create a double, something like:
let(:quickbooks_service) { instance_double(QuickbooksService) }
describe("#create_invoice") do
before { allow(quickbooks_service).to receive(:create_invoice).and_return(1) }
it "Creates quickbook invoice" do
order.create_invoice
expect(quickbooks_service).to have_received(:create_invoice)
end
end
See: https://relishapp.com/rspec/rspec-mocks/docs

The problem is that you are instantiating the class while stubbing it allow(QuickbooksService.new(:estimate)).to receive(:form_invoice_create).with(anything()).and_return(1)
Try this:
allow_any_instance_of(QuickbooksService).to receive(:form_invoice_create).with(anything()).and_return(1)

Related

Spying on Classes of the Same Namespace

I'm creating spies for two classes belonging to the same namespace with the goal of expecting each to receive specific arguments:
allow(SibApiV3Sdk::SendSmtpEmail).to receive(:new).and_return(seb_send_email)
allow(SibApiV3Sdk::SMTPApi).to receive(:new).and_return(seb_smtp_api)
def seb_send_email
#seb_smtp_api ||= SibApiV3Sdk::SendSmtpEmail.new(email_params)
end
def seb_smtp_api
#seb_smtp_api ||= SibApiV3Sdk::SMTPApi.new
end
When I do, the second spy fails to work properly and returns the first spied object instead. I suspect this has something to do with it being a namespaced class. Is this the expected behavior and is there an alternative approach for handling namespaced class spies?
You assign both to #seb_smtp_api variable and that's the source of your problems.
You probably call the seb_send_email method first, then it's memoized as #seb_smtp_api and when you call seb_smtp_api it just returns the memoized value.
You can check that by replacing allow with expect and see that SibApiV3Sdk::SMTPApi's new method is never called.

How can I simply test a method static method call using Minitest?

The class I am testing has a method:
def configuration_data(organization_id, plan_year_id)
Benefit::BenefitConfiguration.new(organization_id, plan_year_id).index
end
I already have a separate test for the BenefitConfiguration index method, so in this case I would like to have code coverage for the configuration_data method. Not sure what the best way to test the method to achieve code coverage is in this case.
My attempt at a test is:
instance = Benefit.new
index_mock = MiniTest::Mock.new
index_mock.expect :index, true
Benefit::BenefitConfiguration.any_instance.stubs(:new).returns(index_mock)
result = instance.configuration_data(1, 2)
assert_equal result, true
But when run, it fails inside of the actual BenefitConfiguration index method, it doesn't seem to be using the mock index return value of true.
Any help would be appreciated!
Benefit::BenefitConfiguration.any_instance.stubs(:new)
any_instance gets triggered when the new method is called on the given class... so this will match when you do something like:
Benefit::BenefitConfiguration.new
However... you are then telling it to stub over the new method on an instance of your class... so you are saying you are looking for code that does this:
Benefit::BenefitConfiguration.new.new
which isn't what you really want.
Probably you want something like:
Benefit::BenefitConfiguration.stubs(:new).returns(index_mock)

Rspec test of private model method in rails app

I have a private model method in my rails app that is so vital to quality of life as we know it that I want to test it using rspec so that it squawks if future tampering changes how it works.
class MyModel < ApplicationRecord
belongs_to something
has_many somethings
after_create :my_vital_method
validates :stuff
private
def my_vital_method
#do stuff
end
end
When I try calling the method in an rspec test I get the following error message:
NoMethodError: undefined method `my_vital_method' for #< Class:......>
Question: How do I call a private model method in rspec?
By definition, you are not allowed to call private methods from outside of the class because they are private.
Fortunately for you, you can do what you want if you use object.send(:my_vital_method) which skips the testing for method call restrictions.
Now, the bigger issue is that you really are making your object more brittle, because calling from outside like that may need implementation details that will get out of sync with the class over time. You are effectively increasing the Object API.
Finally, if you are trying to prevent tampering, then you are tilting at windmills -- you can't in Ruby because I can just trivially redefine your method and ensure that if my new method is called from your anti-tamper checking code, I call the original version, else I do my nefarious stuff.
Good luck.
If your private method needs testing it probably should be a public method of another class. If you find the name of this class define its purpose and then create the method signature you want you would end up with something like
def my_vital_method
MyVitalClass.new.do_stuff
end
and now you can write a spec for do_stuff.

How to stub static class methods through rspec

I have a static method which initiates a static variable by making a external service call. I want to stub that static method call so that external service call is not made while initializing of the class variable.
here is an example of my code in simple terms.
class ABC
def self.ini
return someCallToMyExternalLibrary # i don't want the execution to go there while testing
end
##config = self.ini
def method1
return ##config['download_URL']
end
end
Now I want to stub the static method call with my object so that ##config is initialized with the response which I want to get.
I have tried several things and I seems that ##config is not initialized with my object but by the implemented call only.
describe ABC do
let(:myObject) { Util.jsonFromFile("/data/app_config.json")}
let(:ABC_instance) { ABC.new }
before(:each) do
ABC.stub(:ini).and_return(myObject)
end
it "check the download url" do
ABC_instance.method1.should eql("download_url_test")
# this test fails as ##config is not getting initialized with my object
# it returns the download url as per the implementation.
end
end
I have even tried stubing in the spec_helper with the though that it will be executed first before the class variable is initialized when execution reaches there, but that also did not help. I am stuck with this now for a while. Someone please be a Savior.
Your problem is that the initialization of ##config is occurring while the class ABC is being loaded and there is no way for you intervene in that process via stubbing. If you cannot stub the external call itself, then the only thing I can think of is to change the class definition to include a separate class initialization method.
Instead of stubbing the ":ini" method, which I suppose that you cannot do because the parser goes through the ABC definition before your call to stub the method, I would suggest that you set the class variable ##config to the value you want on your before block:
before(:each) do
ABC.class_variable_set(:##config, myObject)
end
Then try to see whether this solves your problem.

Accessing class method from an instance method

Say I have the following code
class Monster
def self.yell
'i am yelling!'
end
def shout_something
Monster.yell
end
end
My yell method is a class method while shout_something is an instance method that calls yell.
Is there anything inherently wrong with doing something like this? For example, is it bad to call a class method from an instance method? I ask because it feels wrong but maybe it's just because I'm a newbie to ruby.
On a side note, I thought doing self.yell instead of Monster.yell would make more sense but it doesn't work.
There's nothing particularly wrong about calling a class method from an instance method. Depending on your intent and how you want people to subclass your Monster, you might want to use this:
def shout_something
self.class.yell
end
So that subclasses can provide their yell class method.

Resources