Not able to use non stubbed method with receive_message_chain rspec - ruby-on-rails

I have a strange issue with receive_message_chain rspec 3.6
allow_any_instance_of(Order).to receive_message_chain('line_items.find')
{LineItem.first}
When i do a order.line_items instead of returning a collection it returns me a <Double (anonymous)> object which is not what i wanted.
Any suggestions??

When you tell RSpec to set receive_message_chain('line_items.find') on a specific object, it needs set the correct stubs in place for the chain to work.
First RSpec stubs the method line_items on the object. Next it needs to stub the method find, but this method needs to be stubbed on the return value of the method line_items. Since we stubbed that method, RSpec needs to come up with a some return value, that can be the target of the stubbing.
So RSpec sets the return value of the method line_items to be an anonymous double that has the the method find stubbed. Because of this once you set the receive_message_chain with 'line_items.find', the object will always return an anonymous double when line_items is called. And since you use allow_any_instance_of, this means that the method line_items is now stubbed on all the instances of Order.
I would recommend thinking about trying to remove the uses of allow_any_instance_of and receive_message_chain from your specs if it is possible, since they are really shortcuts to be used in specs if you really cannot change the design of your code. https://relishapp.com/rspec/rspec-mocks/docs/working-with-legacy-code/message-chains
However if that is not possible, you can remove the receive_message_chain and set the chain yourself.
line_items_stub = double
# or line_items_stub = Order.new.line_items
# or line_items_stub = [LineItem.new, LineItem.new]
# or line_items_stub = the object that you want
allow_any_instance_of(Order).
to receive(:line_items).
and_return(line_items_stub)
allow(line_items_stub).to receive(:find)
If you were able to remove the allow_any_instance_of you could use and_call_original to call the for real implementation of line_items on that concrete instance you are stubbing the message_chain on.

Related

How do I expect a method to be run with specific ActiveRecord parameters

Using Mocha on Rails 4.2.
I'm testing a method that it should make a call to another method with the correct parameters. These parameters are ActiveRecord objects that it calls up from the database. Here is the key line in my test:
UserMailer.expects(:prompt_champion).with(users(:emma), [[language, 31.days.ago]]).once
Both users(:emma) and language are ActiveRecord objects.
Even though the correct call is made, the test fails because the parameters don't match the expectations. I think this might be because it's a different Ruby object each time a record is pulled up from the database.
I think one way around it is to see what method is being used in my code to pull up the records and stub that method to return mocks, but I don't want to do this because a whole bunch of Records are retrieved then filtered down to get to the right one, mocking all those records would make the test way too complex.
Is there a better way of doing this?
You could use block form of allow/expect.
expect(UserMailer).to receive(:prompt_champion) do |user, date|
expect(user.name).to eq "Emma"
expect(date).to eq 31.days.ago # or whatever
end
Sergio gave the best answer and I accepted it. I discovered the answer independently and found out along the way that I needed to return a mock from the ActionMailer method to make everything work properly.
I think it best to post here my complete test here for the sake of any other hapless adventurer to come this way. I'm using Minitest-Spec.
it 'prompts champions when there have been no edits for over a month' do
language.updated_at = 31.days.ago
language.champion = users(:emma)
language.save
mail = mock()
mail.stubs(:deliver_now).returns(true)
UserMailer.expects(:prompt_champion).with do |user, languages|
_(user.id).must_equal language.champion_id
_(languages.first.first.id).must_equal language.id
end.once.returns(mail)
Language.prompt_champions
end
You could use an RSpec custom matcher and compare expected values in that function.

How to mock message chaining with RSpec?

I have a statement like Model.some_scope.pluck(:a_field) in a method I'd like to test.
What's the recommended way for me to stub the return value of this chained method call statement with rspec-mocks 3.4.0? I saw that stub_chain and receive_message_chain are both listed as old syntax on the RSpec website.
Best Regards.
The cleanest way to test that code is to extract a method, e.g.
class Model < ActiveRecord::Base
def self.some_scope_fields
some_scope.pluck(:a_field)
end
end
That method can be stubbed without a chain.
However, there are times when it's neither convenient nor idiomatic to do that. For example, it's idiomatic, and not a Law of Demeter violation, to call create on an ActiveRecord model's association methods. In those cases,
if you don't care about method arguments, use receive_message_chain. stub_chain is deprecated; receive_message_chain is not. What the receive_message_chain documentation says is that "support for stub_chain parity may be removed in future versions". The issue linked to from that documentation makes it clear that "stub chain parity" means the use of with with receive_message_chain. So do use receive_message_chain; just don't use with with it.
if you do care about method arguments, use doubles. For example, for the code you gave,
scope = double
allow(scope).to receive(:pluck).with(:a_field) { # return whatever }
allow(Model).to receive(:some_scope).with(no_args) { scope }

How to test if method is called in RSpec but do not override the return value

There are already questions similar to this, but they all override the return values to nil unless .and_return is called as well
PROBLEM
I am wondering if there is a way to just check if a method is called using expect_any_instance_of(Object).to receive(:somemethod) and it runs normally without overriding or affecting the return value of .somemethod.
rspec-3.4.0
rails 4.2
Consider the following:
# rspec
it 'gets associated user' do
expect_any_instance_of(Post).to receive(:get_associated_user)
Manager.run_processes
end
# manager.rb
class Manager
def self.run_processes
associated_user = Category.first.posts.first.get_associated_user
associated_user.destroy!
end
end
The spec above although will work because :get_associated_user is called in the run_processes, however it raises NoMethodError: undefined method 'destroy!' for NilClass precisely because I mocked the :get_associated_user for any instance of Post.
I could add a .and_return method like expect_any_instance_of(Post).to receive(:get_associated_user).and_return(User.first) so that it will work without raising that error, but that already is a mocked return value (which might affect the rest of the code below it), and not the correct expected value it should have returned at the time the method is called.
I can however specify .and_return(correct_user) where correct_user is the user that is going to be the same return value as if it has ran normally. However, this will need me to mock every return value in the sequence Category.first.posts.first.get_associated_user just so that it will work normally. The actual problem is a lot more complex than above, therefore stubbing is not really a possible solution in my case.
You can use and_call_original on the fluent interface to "pass
through" the received message to the original method.
https://www.relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method
expect_any_instance_of(Post).to receive(:get_associated_user).and_call_original
However the use of expect_any_instance_of might be telling you that you have a code smell and you should be testing the behavior - not the implementation.
# test what it does - not how it does it.
it 'destroys the associated user' do
expect { Manager.run_processes }.to change(Category.first.posts.first.users, :count).by(-1)
end

Rspec testing: NotificationMailer.user_notification(self) returning nil

I'm having trouble with one of my tests
Rspec
it 'should call send_email_notification on NotificationMailer' do
expect(NotificationMailer).to receive(:user_notification).with(an_instance_of(User))
FactoryGirl.create(:user, shop: Shop.new)
end
Method:
def send_email_notification
if user? && self.shop.email_notifications
NotificationMailer.user_notification(self).deliver_now
end
end
undefined method `deliver_now' for nil:NilClass
Which means NotificationMailer.user_notification(self) is returning nil during the tests. But when I run binding.pry in real local environment, NotificationMailer.user_notification(self) returns the proper object. Which means my tests aren't working...
What would you fix?
Using expect.to receive is a mock - basically a stub with an expectation (purists will probably disagree, but whatever). You're stubbing out the method that you've put the expectation on, so it doesn't get called. Typically you would also specify the return value, so the rest of the code you're testing will continue to work with that return value.
You're not specifying the return value here, so the mock is returning nil, making the rest of your code (that depends on the real return value) blow up.
There are two typical courses of action here:
Use .and_call_original on the end of your mock - this basically means that the mock won't act like a stub, and it will call the original method with the arguments you passed in. You probably don't want that in this case, because it's a mailer and you don't want to send email in your specs.
Specify the return value of the stub, with .and_return. In this case, you might want something like:
expect(NotificationMailer).to receive(:user_notification).with(an_instance_of(User)).and_return(double(deliver: true))
This will return a test double to your code when you call NotificationMailer.user_notification, that responds to the deliver method and will return true.
More information on test doubles can be found in the RSpec Mocks docs:
https://relishapp.com/rspec/rspec-mocks/docs

Rspec mocks, can 'expect' also stub a method as a side effect?

I'm trying to make sense of the tests in an inherited app, and I need some help.
There are lots of spec groups like this one (view spec):
let(:job_post) { FactoryGirl.create(:job_post) }
# ...
before do
expect(view).to receive(:job_post).at_least(:once).and_return(job_post)
end
it "should render without error" do
render
end
... with job_post being an helper method defined on the controller. (yes, they could have used #instance variables, and I'm in the process of refactoring it).
Now, in my opinion using an expect inside a before block is wrong. Let's forget about that for a second.
Normally the test above is green.
However, if I remove the expect line, the test fails. It appears that in this case expect is stubbing the method on the view. In fact, replacing expect with allow seems to have exactly the same effect.
I think that what's going on is that normally – when run with a server – the view will call job_posts and the message will land on the helper method on the controller, which is the expected behaviour.
Here, however, expect is setting an expectation and, at the same time, stubbing a method on the view with a fixed return value. Since the view template will call that method, the test passes.
About that unexpected "stub" side effect of expect, I've found this in the rspec-mocks readme:
(...) We can also set a message expectation so that the example fails if find is not called:
person = double("person")
expect(Person).to receive(:find) { person }
RSpec replaces the method we're stubbing or mocking with its own test-double-like method. At the end of the example, RSpec verifies any message expectations, and then restores the original methods.
Does anyone have any experience with this specific use of the method?
Well, that's what expect().to receive() does! This is the (not so) new expectation syntax of rspec, which replaces the should_receive API
expect(view).to receive(:job_post).at_least(:once).and_return(job_post)
is equivalent to
view.should_receive(:job_post).at_least(:once).and_return(job_post)
and this API sets the expectation and the return value. This is the default behavior. To actually call the original method as well, you need to explicitly say so:
view.should_receive(:job_post).at_least(:once).and_call_original
On to some other issues:
(yes, they could have used #instance variables, and I'm in the process of refactoring it).
let API is very ubiquitous in rspec testing, and may be better than #instance variables in many cases (for example - it is lazy, so it runs only if needed, and it is memoized, so it runs at most once).
In fact, replacing expect with allow seems to have exactly the same effect.
The allow syntax replaces the stub method in the old rspec syntax, so yes, it has the same effect, but the difference is, that it won't fail the test if the stubbed method is not called.
As the OP requested - some explanations about should_receive - unit tests are expected to run in isolation. This means that everything which is not directly part of your test, should not be tested. This means that HTTP calls, IO reads, external services, other modules, etc. are not part of the test, and for the purpose of the test, you should assume that they work correctly.
What you should include in your tests is that those HTTP calls, IO reads, and external services are called correctly. To do that, you set message expectations - you expect the tested method to call a certain method (whose actual functionality is out of the scope of the test). So you expect the service to receive a method call, with the correct arguments, one or more times (you can explicitly expect how many times it should be called), and, in exchange for it actually being called, you stub it, and according to the test, set its return value.
Sources:
Message expectation
RSpec's New Expectation Syntax
Rspec is a meta-gem, which depends on the rspec-core, rspec-expectations and rspec-mocks gems.
Rspec-mocks is a test-double framework for rspec with support for method stubs, fakes, and message expectations on generated test-doubles and real objects alike.
allow().to receive
is the use of 'Method Stubs', however
expect().to receive
is the use of 'Message Expectations'
You can refer to the Doc for more details
If you don't want to stub as a side affect, you can always call the original.
https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method
For example I once wanted to spy on a method, but also call the function else it has other side affects. That really helped.
https://relishapp.com/rspec/rspec-mocks/v/2-14/docs/message-expectations/calling-the-original-method

Resources