How can I simply test a method static method call using Minitest? - ruby-on-rails

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)

Related

RSpec mock service and corresponding method

I want to mock a service that returns the integer 0 when called with get_auth_key.api_response_code.
This is what I have so far:
before(:each) do
PolicyObjects::BpointRequestPolicy.any_instance.stub(:get_auth_key).and_return(OpenStruct.new(api_response_code: 0))
end
I would like to avoid creating a class for this in the test as I believe it's overkill. Somewhere in the codebase, response = PolicyObjects::BpointRequestPolicy.new(...) gets called and then compared: if response.api_response_code == 0. I would like it to return 0 to hit conditionals in my test.
What is the best practice or easiest way of doing this?
This is a great use case for a double or better an instance_double if you know the class you're mocking out:
let(:api_resonse) { instance_double("SomeResponseClass", api_response_code: 0) }
before(:each) do
PolicyObjects::BpointRequestPolicy.any_instance.stub(:get_auth_key).and_return(api_response)
end

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.

RSpec Stubbing Service Object Method

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)

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.

Stubbing a mock object does not work

I have a factory class carfactory that i create a mock object of.
def mockCar = Mock(Car)
def mockFactory = Mock(CarFactory)
this.mockCar.getDoors(_) >> ["a","b"]
this.mockFactory.getCar(_) >> this.mockCar
Now when the test runs i get a mockCar object from the factory but not the set from the getDoors(_) call.
I get a empty set.
The usage of this. is incorrect because mockCar and mockFactory are local variables, not instance fields. After fixing this, the shown code works fine (I just double-checked in Spock Web Console). If you get back an empty set, chances are that your real code has another problem not shown here.

Resources