How to stub static class methods through rspec - ruby-on-rails

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.

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.

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.

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)

Accessing instance method from class method

I have a rails 4 app that has an alert model and tests associated to each alert.
When a new alert is created I have a an after_create filter that uses an instance method to create a new test:
class Alert < ActiveRecord::Base
has_many :tests
after_create :create_test
private
def create_test
#bunch of code using external api to get some data
Test.create
end
end
I also have a cron job that I want to use to create a new test for each alert. My plan was to have a class method to do that:
def self.scheduled_test_creation
#alerts = Alert.all
#alerts.each do |a|
a.create_test
end
end
That won't work because the instance method is private. I know I can get around this using send for example. Or I can make the methods public. Or I can rewrite that bunch of api code in the instance method.
I am just not sure what the best way would be. I don't want to write the same code twice and I want to make sure is good practice. Maybe in this case the methods don't have to be private - I know the difference between public/private/protected but I don't really understand when methods should be private/protected.
Any help would be greatly appreciated
I like service classes for interactions between multiple models. Callbacks can make the logic quite hard to follow.
Eg:
class AlertCreator
def initialize(alert)
#alert = alert
end
def call
if #alert.save
alert_test = TestBuilder.new(#alert).call
alert_test.save
true
end
end
end
class TestBuilder
def initialize(alert)
#alert = alert
end
def call
# external API interaction stuff
# return unsaved test
end
end
Inside your controller, you'd call AlertCreator.new(#alert).call instead of the usual #alert.save.
I agree with #SergioTulentsev: while in the long run you may be better served by breaking out this logic into a service class, in the short run you simply shouldn't make a method private if it needs to be called outside of the instance.
In some cases you actually want to access a private method, for example when verifying object state during tests. This is easy to do:
#alert.instance_eval{ create_test }
You can even fetch or alter instance variables this way:
#alert.instance_eval{ #has_code_smells = true }
In general, if you feel the need to do this, it's a warning smell that your logic needs to be rethunk. Ignoring that sort of smell is what turns Ruby from a wonderful language into a way-too-powerful language that allows you to shoot yourself in the foot. But it's doable.

What is the difference between instance&class method include&extend (Ruby, Rails)

What's the difference between class method and instance method.
I need to use some functions in a helper "RemoteFocusHelper" (under app/helpers/)
Then include the helper "RemoteFocusHelper" in the Worker module
But when I tried to call 'check_environment' (defined in RemoteFocusHelper),
It raised ""no method error"".
Instead of using "include", I used the "extend" and works.
I wonder know if it is correct that we can only use a class method when in a class method.
Is it possible to call a instance method in a class method ?
By the way,how does the rake resque:work QUEUE='*' know where to search the RemoteFocusHelper I didn't give it the file path.Is the rake command will trace all files under the Rails app?
automation_worker.rb
class AutomationWorker
#queue = :automation
def self.perform(task=false)
include RemoteFocusHelper
if task
ap task
binding.pry
check_environment
else
ap "there is no task to do"
end
end
end
The difference is the context where you're executing. Pretty much every tutorial will have include or extend under the class:
class Foo
include Thingy
end
class Bar
extend Thingy
end
This will get executed at the time the class is defined: self is Foo (or Bar) (of type Class). extend will thus dump the module contents into self - which creates class methods.
When you do it inside a method definition, self is the instance object (of type Foo or Bar). Thus the place where the module gets dumped into changes. Now if you extend (the module contents), it dumps them into what is now self - resulting in instance methods.
EDIT: It is also worth noting that because extend works on any instance object, it is defined on Object. However, since only modules and classes are supposed to be able to include stuff, include is an instance method of Module class (and, by inheritance, Class as well). As a consequence of this, if you try putting include inside a definition of an instance method, it will fail hard, since most things (including your AutomationWorker) are not descended from Module, and thus do not have access to the include method.

Resources