How to stub a method that is called in initialize method - ruby

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

Related

How to properly stub doubles

Code being tested:
class Session
def initialize
#interface = Interface.new(self)
#interface.hello
end
end
class Interface
def initialize(session, out = $STDOUT)
#session = session
#out = out
end
def hello
#out.puts "hello"
end
end
Test:
describe Session do
let (:fake_stdout) {double("$STDOUT", :puts => true)}
let (:interface) {instance_double("Interface", :out => "fake_stdout")}
let (:session) { Session.new }
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session)
end
end
end
This throws private method 'puts' called for nil:NilClass. It seems it's not seeing the fake_stdout with its specified :puts as out. I tried tying it with allow(Interface).to receive(:new).with(session).and_return(interface), but that changed nothing. How do I get the tested Session class to see the double/instance double and pass the test?
I think, this is not really problem with stubbing, but the general approach. When writing your unit tests for some class, you should stick to functionality of that class and eventually to API it sees. If you're stubbing "internal" out of Interface - it's already to much for specs of Session.
What Session really sees, is Interfaces public hello method, thus Session spec, should not be aware of internal implementation of it (that it is #out.puts "hello"). The only thing you should really focus is that, the hello method has been called. On the other hand, ensuring that the put is called for hello should be described in specs for Interface.
Ufff... That's long introduction/explanation, but how to proceed then? (known as show me the code! too ;)).
Having said, that Session.new should be aware only of Interfaces hello method, it should trust it works properly, and Sessions spec should ensure that the method is called. For that, we'll use a spy. Let's get our hand dirty!
RSpec.describe Session do
let(:fake_interface) { spy("interface") }
let(:session) { Session.new }
before do
allow(Interface).to receive(:new).and_return(fake_interface)
end
describe "#new" do
it "creates an instance of Session" do
expect(session).to be_an_instance_of(Session) # this works now!
end
it "calls Interface's hello method when initialized" do
Session.new
expect(fake_interface).to have_received(:hello)
end
end
end
A test spy is a function that records arguments, return value, the value of this and exception thrown (if any) for all its calls.
This is taken from SinonJS (which is the first result when googling for "what is test spy"), but explanation is accurate.
How does this work?
Session.new
expect(fake_interface).to have_received(:hello)
First of all, we're executing some code, and after that we're asserting that expected things happened. Conceptually, we want to be sure, that during Session.new, the fake_interface have_received(:hello). That's all!
Ok, but I need another test ensuring that Interfaces method is called with specific argument.
Ok, let's test that!
Assuming the Session looks like:
class Session
def initialize
#interface = Interface.new(self)
#interface.hello
#interface.say "Something More!"
end
end
We want to test say:
RSpec.describe Session do
describe "#new" do
# rest of the code
it "calls interface's say_something_more with specific string" do
Session.new
expect(fake_interface).to have_received(:say).with("Something More!")
end
end
end
This one is pretty straightforward.
One more thing - my Interface takes a Session as an argument. How to test that the interface calls sessions method?
Let's take a look at sample implementation:
class Interface
# rest of the code
def do_something_to_session
#session.a_session_method
end
end
class Session
# ...
def another_method
#interface.do_something_to_session
end
def a_session_method
# some fancy code here
end
end
It won't be much surprise, if I say...
RSpec.describe Session do
# rest of the code
describe "#do_something_to_session" do
it "calls the a_session_method" do
Session.new.another_method
expect(fake_interface).to have_received(:do_something_to_session)
end
end
end
You should check, if Sessions another_method called interfaces do_something_to_session method.
If you test like this, you make the tests less fragile to future changes. You might change an implementation of Interface, that it doesn't rely on put any more. When such change is introduced - you have to update the tests of Interface only. Session knows only the proper method is called, but what happens inside? That's the Interfaces job...
Hope that helps! Please, take a look at another example of spy in my other answer.
Good luck!

How to stub a instance method for a specific instance?

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

How do I make a class conditionally return one of two other classes?

I have a design problem.
I'm writing a REST client in ruby. For reasons beyond my control, it has to extend another gem that uses my networks zookeeper instance to do service lookup. My client takes a user provided tier, and based on that value, queries the zookeeper registry for the appropriate service url.
The problem is that I also need to be able to run my client against a locally running version of the service under test. When the service is running locally, zookeeper is obviously not involved, so I simply need to be able to make GET requests against the localhost resource url.
When a user instantiates my gem, they call something like:
client = MyRestClient.new(tier: :dev)
or in local mode
client = MyRestClient.new(tier: :local)
I would like to avoid conditionally hacking the constructor in MyRestClient (and all of the GET methods in MyRestClient) to alter requests based on :local vs. :requests_via_the_zk_gem.
I'm looking for an elegant and clean way to handle this situation in Ruby.
One thought was to create two client classes, one for :local and the other for :not_local. But then I don't know how to provide a single gem interface that will return the correct client object.
If MyClient has a constructor that looks something like this:
class MyClient
attr_reader :the_klass
def initialize(opts={})
if opts[:tier] == :local
#the_klass = LocalClass.new
else
#the_klass = ZkClass.new
end
#the_klass
end
end
then I end up with something like:
test = MyClient.new(tier: :local)
=> #<MyClient:0x007fe4d881ed58 #the_klass=#<LocalClass:0x007fe4d883afd0>>
test.class
=> MyClient
test.the_klass.class
=> LocalClass
those who then use my gem would have to make calls like:
#client = MyClient.new(tier: :local)
#client.the_klass.get
which doesn't seem right
I could use a module to return the appropriate class, but then I'm faced with the question of how to provide a single public interface for my gem. I can't instantiate a module with .new.
My sense is that this is a common OO problem and I just haven't run into it yet. It's also possible the answer is staring me in the face and I just haven't found it yet.
Most grateful for any help.
A common pattern is to pass the service into the client, something like:
class MyClient
attr_reader :service
def initialize(service)
#service = service
end
def some_method
service.some_method
end
end
And create it with:
client = MyRestClient.new(LocalClass.new)
# or
client = MyRestClient.new(ZkClass.new)
You could move these two into class methods:
class MyClient
self.local
new(LocalClass.new)
end
self.dev
new(ZkClass.new)
end
end
And instead call:
client = MyRestClient.local
# or
client = MyRestClient.dev
You can use method_missing to delegate from your client to the actual class.
def method_missing(m, *args, &block)
#the_class.send(m, *args, &block)
end
So whenever a method gets called on your class that doesn't exist (like get in your example) it wil be called on #the_class instead.
It's good style to also define the corresponding respond_to_missing? btw:
def respond_to_missing?(m, include_private = false)
#the_class.respond_to?(m)
end
The use case you are describing looks like a classic factory method use case.
The common solution for this is the create a method (not new) which returns the relevant class instance:
class MyClient
def self.create_client(opts={})
if opts[:tier] == :local
LocalClass.new
else
ZkClass.new
end
end
end
And now your usage is:
test = MyClient.create(tier: :local)
=> #<LocalClass:0x007fe4d881ed58>
test.class
=> LocalClass

ruby, no method error

I am receiving the following error when running my below ruby script:
s3parse.rb:12:in `block in <class:AccountLog>': undefined method `extract_account_id' for AccountLog:Class (NoMethodError)
I dont think it should be a class method, is there a reason its not taking my method into account?
class AccountLog
attr_accessor :bytes, :account_id, :date
def extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
end
def extract_account_id will define an instance method.
In the way you call it, you need a class method instead.
Define it like this:
def self.extract_account_id(line)
or, as you already have an AccountLog instance, use it to call extract_account_id:
account_log.account_id = account_log.extract_account_id(line)
Please note that with second way you do not need to alter method definition, just call extract_account_id via account_log instance.
And i guess you would want to put s3log = File... outside class definition.
Or use a constant instead: S3log = ...
Then you'll can access it as AccountLog::S3log
Is there any reason you don't think it should be a class method? You are using it in the context of a class method and that's why it it's saying no such method for class AccountLog.
If you name your method as self.extract_account_id(line) I'm sure it will work.
From what you are trying to do I think this is what you are looking for?
class AccountLog
attr_accessor :bytes, :account_id, :date
def self.extract_account_id(line)
line.match(%r{accounts/(\d+)}).captures.join.to_i
end
end
s3log = File.open('vidcoder.txt').each do |line|
account_log = AccountLog.new
account_log.date = line.match(%r{\[[^:]*}).to_s.delete"[" #need to finish this regex to make it work
account_log.account_id = extract_account_id(line)
account_log.bytes = line.match(%r{^.*\s+HTTP.*\s+-\s+(\d+)\s+}).captures.join.to_i
puts "\n"
puts "The api request on #{account_log.date} was fromm account number #{account_log.account_id} and the bytes were #{account_log.bytes}"
end
While you could take the class method approach, there seems to be a little more going on.
You should put the extraction logic in a method in itself rather than let it hangout in your class. Then outside of the class, have an instance of AccountLog where you can call on the methods for log and account id extraction. At that point you can do something with those values.
Class method or not is a detail we can explore after the class is a bit more clean I think.

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).

Resources