Observing strange behavior with `allow` and `have_received` with rspec - ruby-on-rails

I'm having issues using allow and have_received in my specs
I have a model called Obj that has a belongs_to relationship with a model called Parent. The Parent model has a has_many relationship with Obj.
In the Obj model, I've defined a method called child_method. In the Parent model, I've defined a method called calls_child_method, which iterates through each Obj associated with it and has them call child_method
I'm writing a spec to test this behavior as follows, but it keeps failing:
describe 'parent calls_child_method' do
let(:obj) { Obj.create }
before do
allow(obj).to receive(:child_method)
end
it 'should call child_method' do
obj.parent.calls_child_method
expect(obj).to have_received(:child_method)
end
end
Output:
expected: 1 time with any arguments
received: 0 times with any arguments
However, this seems to pass when I use allow_any_instance_of to do the spy/stubbing:
describe 'parent calls_child_method' do
let(:obj) { Obj.create }
before do
allow(obj).to receive(:child_method)
end
it 'should call child_method' do
expect_any_instance_of(Obj).to receive(:child_method)
obj.parent.calls_child_method
end
end
or if I call the child method directly:
describe 'parent calls_child_method' do
let(:obj) { Obj.create }
before do
allow(obj).to receive(:child_method)
end
it 'should call child_method' do
obj.child_method
expect(obj).to have_received(:child_method)
end
end
In all of this, I've verified that the instance of the Obj that gets created is actually calling child_method by using byebug debugging to see that it is being called.
Can someone help me understand why the spec/spy is behaving like this?

This problem was puzzling me for many years. This is how I understand what is happening.
In your failing case obj is a reference to your newly created Obj.create. By calling expect(obj).to have_received(:child_method) it is expected that EXACT reference obj should receive child_method.
When obj’s parent receives calls_child_method it iterates through each Obj associated with it. Most probably it calls parent.objs which will trigger a new DB call. In your case that will find your obj, but will have a different reference to it. And that different reference will be the one that gets child_method
That’s why technically your object does receive the method call, but through a different reference. As a result your expectation fails:(
expect_any_instance_of(Obj) solves the problem. However it’s usually better to avoid it.
Your last example is successful because your expectation and method call is using the same object/reference.

Related

Where should method go (model?, somewhere else)

I've got a method in one of my models which returns data which will be fed into a charting gem.
class MyModel < ActiveRecord::Base
def ownership_data
format_data(item_ownerships.group(:owned).count)
end
end
I need to guarantee that the data return always has 2 values in the result. Something like this:
{ "yes" => 4, "no" => 2 }
In order to do this, I've written another method which is used in the first method:
def format_data(values)
values[false].nil? ? values = values.merge({ "no" => 0 }) : true
values[true].nil? ? values = values.merge({ "yes" => 0 }) : true
return values
end
My question is, where should this method go and how can I unit test it using rspec? I've currently got it in the model, however in trying to test it with rspec, my specs look like this:
let(:values) { { "yes" =>2 } }
it "it will return 2 values" do
result = MyModel.new.format_data(values)
expect(result.keys.count).to eq(2)
end
I'm not too happy about having to instantiate an instance of the model to test this. Any guidance is appreciated.
As AJ mentioned in the comment, we probably need more information to go on to give more specific advice. I'll give some anyway...
If you have a object that isn't necessarily depending on the model itself, you should consider moving it to a plain old ruby object or a service object. Those can live in a separate folder (lib/services) or in your models folder without inheriting from ActiveRecord::Base.
Your method could also be a class method def self.method_name(values, values_2) instead of a instance method (if you don't need specific values from the model).
When it comes to data manipulation for reporting for my projects, I've built specific folder of ruby service objects for those under 'lib/reports' and they take raw data (usually in init method) and return formatted data from a method call (allowing me to have multiple calls if the same data can be formatted in different output options). This makes them decoupled from the model. Also, this makes testing easy (pass in known values in Class.new expect specific values in method outputs.

Can't access one model's instance variables from another model) (self.ruby)

I have two models, Draft and Pick. Draft creates an array of available Players in an instance variable named 'available_players'. This is done using the 'before_save' callback. The callback runs the instance method 'start' which in turn runs 'set_active_players'. I've tested all of this in my Draft_spec and I have no problems loading players and having them returned in the available_players array. All my draft specs pass.
The problem is that when I try to access the 'available_players' instance variable from Pick.rb, it returns nil. If I call 'draft.start' (the instance method that should run before Draft.rb saves), I can suddenly access the 'available_players' array... it's like the Draft object is not creating the available_players array even though I have the before_save method in place.
Here is the code that fails inside of Pick.rb:
def available_players_returns_nil
#draft_object.available_players
end
Here is the code that works inside of Pick.rb:
def available_players_working
#draft_object.start
#draft_object.available_players
end
I don't want to have to call start every time I call the method because available_players should not need to reload ALL Players. Please help me access available_players!
Links: failing Pick specs, Pick.rb
EDIT:
I should add that #draft_object is found using
#draft_object = Draft.find(self.draft_id)
For a start, this is wrong:
#draft_object = Draft.find(self.draft_id)
You have an association set up, so use it. You can simply use draft within your Pick object to access the Draft it belongs to. No need to assign it to an instance variable called #draft_object.
Same story with player.
Incidentally, your set_available_players method in Draft is just looping through all of the players and adding them to an instance variable. Why are you doing this? Why don't you simply grab the players directly if you need them in Pick? Like this:
#players = Player.all
Also ... I'm somewhat concerned that pretty much all of your tests are commented out. I hope that's not by design?

Rails / Rspec: .should_receive(method).with(parameter) where parameter is not known until tested method is called

I have a method that creates a new object and calls a service with that new object as a parameter, and returns the new object. I want to test in rspec that the method calls the service with the created object, but I don't know the created object ahead of time. Should I also stub the object creation?
def method_to_test
obj = Thing.new
SomeService.some_thing(obj)
end
I'd like to write:
SomeService.stub(:some_thing)
SomeService.should_receive(:some_thing).with(obj)
method_to_test
but I can't because I don't know obj until method_to_test returns...
Depending whether or not it's important to checkobj you can do:
SomeService.should_receive(:some_thing).with(an_instance_of(Thing))
method_to_test
or:
thing = double "thing"
Thing.should_receive(:new).and_return thing
SomeService.should_receive(:some_thing).with(thing)
method_to_test

Can one easily stub methods on all instances of a specific Activerecord?

Say I want to make a test for a method that retrieves records. For one of the records I'd like record.remote to be stubbed to return a certain object and for others to return some other object. Class.any_instance comes close to what I want, but I'd like to be able to filter the instances down to those coming from a specific record.
Something like this would be OK if it would work.
Answer.any_instance.stub(:remote).and_return do
if self.id == #answer_2.id
remote_answer
else
remote_complete_answer
end
end
Except that self is not an Answer in this case but an RSpec::Core::ExampleGroup. Can I get to the original object in an and_return block?

should_receive in RSpec

As far as I know, should_receive is applied only to mock objects. What I want is to check, if a certain Class (not object) received a certain message, like:
User.should_receive(:all).once
How do I do that?
UPD. Commonly, writing test for models and controllers we can write User.should_receive(:smth).once. But in my case I'm testing an arbitrary class from the lib folder, and somehow I always receive the following message:
<User( [fields] ) (class)> expected :all with (no args) once, but received it 0 times>
Any ideas on why is that so? A test somehow sees the User class, but can't check if it receives a message. Of course I've ten times checked that the User is actually getting a message.
Easy:
User.should_receive(:all).once
What I want is to check, if a certain Class (not object) received a certain message
A class is an object!

Resources