How to stub a instance method for a specific instance? - ruby

I have below case,
class Schools::Status
def initialize(school)
#school = school
end
def active?
true
end
end
Now, I want to stub active? method for a specific school.
One way to have like this
Schools::Status.new(school).stubs(:active?).returns(false)
But my use case is different, I have search result of schools and I want to filter that result based on active? value as below:
schools.select { |s| Schools::Status.new(school).active? }
In above case, specifically I want to stub active? for certain instance.

Just monkey-patch your class in the spec.The more rspec-way would be to use any_instance with stub but the problem is you cannot get access to the self of the stubbed instance so you practicly have no information about the school and you cannot access it in that block.
Example:
Status.any_instance.stub(:active?) { # no way to access school }

I found answer on myself and putting here so that others can be benefited
Lets say, I have school for which active? method of Schools::Status is to be stubbed.
In order to achieve this,
First we need to stub new method of Schools::Status so that it will return Schools::Status instance which we want and it can be done as below -
status = Schools::Status.new(school)
# now whenever Schools::Status instance getting created for ours school
# it will return our defined status instance
Schools::Status.stubs(:new).with(school).returns(status)
Secondly, we have to stub active? method for status instance -
status.stubs(:active?).returns(false)
Now, filter will reject specified school for which active? method returns false

Related

Can an Abstract Factory be responsible for "creating or finding an existing" item?

My Ruby code has a Concrete Factory, which builds some complex objects:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
# ... Some data preparation and defaults
Author.new(
email: email
# Map and assign more attributes
)
end
end
end
Now, I've run into a situation where I either need to build a new one,
or assign one from an existing collection. In
database-terms: an UPSERT, or in ActiveRecord: find_or_create_by.
And I am not sure if this:
Is a proper task for an Abstract Factory and
If the proper way to implement this is by passing the collection, or
to make the Factory itself responsible for fetching it.
Passing it in:
author = Author::Factory.build(email: "john#example.com", existing: authors)
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = existing.find {|author| author.email == email }
# If not found, prepare and build a new one, like above.
end
end
end
Letting the Factory find it:
author = Author::Factory.build(email: "john#example.com")
class Author
class Factory < BaseFactory
def self.build(email: nil)
author = Author.find_in_existing_with(email: email)
# If not found, prepare and build a new one, like above.
end
end
end
So: Should a Factory every be responsible for finding-or-building?
And if so, must the Factory be responsible for fetching the items that
it must match against, or should the caller pass them along?
Factory is a creational pattern, so clients will expect fresh new instances out of it.
Sure, what the Factory does internally is of no concern to consuming code. But if Author is a domain entity, I fail to see how an Author-building object could be used by consumers for anything else than the "real world" addition of a new author in the system.
Unless you want be semantically unfair and trick callers by reusing existing authors instead of instantiating new ones. But that doesn't look like something you would typically do in production.

how do I use rspec to test instance methods in ruby?

I have an instance method that would be invoked after creating a new instance of the class.
How do I test it in Rspec? I use the following and get an error:
let(:schedule) { ScheduleKaya.new('test-client-id') }
let(:schedule) { schedule.create_recurring_event('test-keyword', 'slack') }
In other words, I want to create the instance. Then I want to apply a method create_recurring_event.
My test wants to check if it assigned the variables to the instance.
it "has #keyword = test-keyword" do
expect(schedule.keyword).to eq('test-keyword')
end
Because it makes a database call, I want to check the response back from the call to see if I get a status = 200.
But I can't seem to both create the instance and then apply the method.
Question:
What is the right way to test for an instance method, one that is applied after creating a new instance.
A let block acts like a method and returns the return value of the last statement. Therefore just write both into the same block and ensure that the right value is returned:
let(:schedule) do
schedule_kaya = ScheduleKaya.new('test-client-id')
schedule_kaya.create_recurring_event('test-keyword', 'slack')
schedule_kaya
end
Or you can use tap:
let(:schedule) do
ScheduleKaya.new('test-client-id').tap do |schedule_kaya|
schedule_kaya.create_recurring_event('test-keyword', 'slack')
end
end
I suggest FactoryGirl together with Rspec, if you are on Railsfactory_girl_rails, looks like below:
it "has #keyword = test-keyword" do
schedule = Factory(:shcedule, keyword: "has #keyword = test-keyword")
expect(schedule.keyword).to eq('test-keyword')
end

How to stub a method that is called in initialize method

I want to stub a method that is called in initialize method.
There is a class Company like this:
class Company
def initialize(code: code, driver: driver)
#driver = driver
#code = code
navigate_to_search_result
end
def navigate_to_search_result
# do something
end
end
And I want to stub the method navigate_to_search_result.
before(:each) do
company = Company.new(code: 7220, driver: Selenium::WebDriver.for(:phantomjs))
allow(company).to receive(:navigate_to_search_result){ true }
end
But this code fails because navigate_to_search_result is already executed by initializing.
How can I stub method like this?
One of the following lines should be present/run in your test before you instantiate a Company object i.e. before you do Company.new.
allow_any_instance_of(Company).to receive(:navigate_to_search_result){ true }
or
allow_any_instance_of(Company).to receive(:navigate_to_search_result).and_return(true)
Move the navigate_to_search_result method out of the initialize method and call it manually. The initialize method is typically only used for setup.
Company.stub(:new).and_return(Object)
Use doubles, like:
before
company = double(Company, code: 7220, driver: Selenium::WebDriver.for(:phantomjs))
allow(company).to receive(:navigate_to_search_result){ true }
end
With doubles, you can fake an instance of Company without initialize it. So it will not try run navigate_to_search_result before you stub it.
But what are you testing? Maybe stub might not be the better choice, and you might test the method navigate_to_search_result

How to return the receiver instance's self from should_receive block

I'd like to have instance methods of a class return self, and be init with another class instance self.
However I'm struggling to see how to spec this succintly:
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
end
When running this spec, this ensures that the next method is called on true instead of the Api instance, as expected, that is the return value of the block. Example:
class Cli
def eg
api = Api.new(self)
api.blowup # undefined method for true
end
end
I'd really like the block to return the Api instance self without invoking another call to Api.new(...) in the spec, the example below does this and to my mind a non-rspec reader would wonder why the spec passes when clearly Api.new(...) has been called more than once.
Can anyone suggest how best to do this?
Current solution:
This reads like ::Api.new(...) is called thrice: once to create api, once to create cli, once to create start. Yet the spec of one call passes. I understand why and that this is correct, so not a bug. However I'd like a spec that a reader not familiar with rspec could scan and not have the impression that Api.new has been called more than once. Also note that ...once.and_return(api){...} does not work, the block needs to return api in order to pass.
let(:cli){ ::Cli.start(['install']) }
let(:start){ ::Cli.start(['install']) }
it 'is the API' do
api = ::Api.new(cli)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
api
end
start
end
You can save the original method (new) in a local variable and then use it to return the new api from within the block:
original_method = ::Api.method(:new)
::Api.should_receive(:new).once do |arg|
arg.should be_an_instance_of(::Cli)
original_method.call(arg)
end
This will run the expectation, checking that the argument is an instance of ::Cli, and then return the value from the original method (i.e. the api).

Alternative initialize for a Class to avoid processing already known information

I have a class, Autodrop, that contains several methods , a.o. 'metadata', that call an external API (dropbox). They are slow.
However, I already often have that metadata around when initializing the AutodropImage, so I should make the methods smarter.
What I have in mind is this:
class Autodrop
include Dropbox
attr_reader :path
def initialize(path)
#path = path
end
def self.from_entry(drop_entry)
#drop_entry = drop_entry
self.initialize(#drop_entry.path)
end
def metadata
if #drop_entry = nil
return heavy_lifting_and_network_traffic
else
return #drop_entry.metadata
end
end
#...
end
Now, I would expect to call
entry = BarEntry.new()
foo = Autodrop.from_entry(entry)
foo.metadata
In order to avoid that heavy lifting and network traffic call.
But this does not work. And somehow, in all my newbieness, I am sure I am goind at this all wrong.
Is there a term I should look for and read about first? How would you go for this?
Note, that the examples are simplified: in my code, I inherit AutodropImage < Autodrop for example, which is called from withing AutodropGallery < Autodrop. The latter already knows all metadata for the AutodropImage, so I mostly want to avoid AutodropImage going over the heavy lifting again.
You are creating an instance variable #drop_entry in your class method from_entry and obviously it wont be available to your object that you are creating in this method. One workaround is to pass it as a parameter when you are initializing the class. It should work if you do the following modifications:
In your from_entry class method change
self.initialize(#drop_entry)
to
new(#drop_entry)
Modify initialize method to:
def initialize(drop_entry)
#drop_entry = drop_entry
#path = #drop_entry.path
end
Or if your class is tied up to pass only the path parameter, ie. you dont want to change the other existing code then you can use an optional parameter drop entry like so
def initialize(path, drop_entry=nil)
You would need to cache the metadata in a class variable.
Edit: Or in a class level instance variable.
Maybe this read will help: http://railstips.org/blog/archives/2006/11/18/class-and-instance-variables-in-ruby/

Resources