Rspec Mocking and return value - ruby-on-rails

I'm trying to integrate my application with the Sendgrid api. For example, when a user registers add the user's email to Sendgrid.
As of now I have the following tests:
context "painter registers" do
let(:new_painter) {FactoryGirl.build(:painter)}
it "#add_email_to_sendgrid is called" do
new_painter.should_receive(:add_email_to_sendgrid)
new_painter.save
end
it "Sendgrid#add_email called and should return true" do
new_painter.email = "nobody-sendgrid#painterprofessions.com"
Sendgrid.should_receive(:add_email).with("nobody-sendgrid#painterprofessions.com").and_return(true)
new_painter.save
end
end
In my Painter model I have the following:
after_create :add_email_to_sendgrid
def add_email_to_sendgrid
Sendgrid.add_email(self.email)
end
lib/sendgrid.rb
module Sendgrid
def self.add_email(email_address)
return false
end
end
Everything is working up until the return value of Sendgrid.add_email.
I can change the add_email methods return value to anything and the test will still pass.
Please advise.

I guess there is confusion in the way you read:
Sendgrid.should_receive(:foo).with(args).and_return(bar)
You can actually return whatever you want, this is not an expectation, it's just a convenient way to simulate a service.
So:
yes, you can return anything you want.
yes, your spec will pass whatever value is returned since you do not spec anything related to it
I guess you expct the fail to fail somehow because the after_create returns false, if so, it's kind of awkward to have a creation depend on an external service.

Related

rails minitest stubbing to assert alternate method

A method replies upon two distinct APIs for geolocation, the second serving as a backup:
def admin_create
#user_object = User.create!(user_params)
set_area(#user_object)
end
def set_area(resource)
do_geocode_lookup(resource)
if !resource.lon
do_alternate_geocode_lookup(resource)
end
end
Finding a data set that returns an empty lon/lat set is challenging (and orthodoxy pushes one to write the test first),
so.. Is there a way to stub the test so that
do_geocode_lookup returns empty lon lat values
do_alternate_geocode_lookup(resource) method gets invoked? and thus tested?
sign_in operator_user
post admin_create_users_url, params: {user: { [...] } }
assert[...]
Using Mocha you can stub out a method so that any instance of an object returns a specified value.
For example....
Account.any_instance.stubs(:reviews_enabled).returns(:true)
It also allows you test that a method was called...
Account.any_instance.expects(:review_started)
The fact you are altering the passed in resource instead of returning something makes things a bit trickier, but I'd be tempted to have do_geocode_lookup return true if it finds something, and false otherwise, to make things a little easier to test.
def set_area(resource)
found = do_geocode_lookup(resource)
unless found
found = do_alternate_geocode_lookup(resource)
end
found
end
Then you could do something like this in your test...
Geocoder.any_instance.stubs(:do_geocode_lookup).returns(:false)
Geocoder.any_instance.expects(:do_alternate_geocode_lookup)
result = sut.set_area(user)
If the expected method isn't called, you get an "expected method wasn't called" error. You can get far more sophisticated with the stubs and expects, but in this case you shouldn't have to.

Rspec expect to receive not working for object in array

I'm sure this is answered somewhere; I can't seem to phrase my google search right though. I'm trying to test that a method is called on an object, but the method isn't called on the specific object in the spec. The method is called on the last item in a collection, which I've confirmed is the same underlying object as the one in the spec. I'm not sure how clear that was, so here is an example:
expect(#email).to receive(:send) # fails
puts #user.emails.last == #email # true
#user.emails.last.send
As a sanity check, this spec passes. However the code I'm testing has #user.emails.last.send in it, so I'm trying to figure out how to make the spec above pass.
expect(#email).to receive(:send) # passes
#email.send
Edit:
#user.emails.last.equal?(#email) returns false, so as suspected by #spickermann
and #Grzegorz the #user.emails.last and #email are two instances of the same object. So I guess what I'm asking is how can I test that the send method was called on a specific object (ignoring what particular instance of that object it was called on). My question is actually the same as this one that I just found Rspec: Test if instance method was called on specific record with ActiveRecord.
It's possible that == method is defined on the mail object in a way that it returns true if some attributes are the same, but it doesn't care if the object is the same.
#user.emails.last == #email
This is the case with a simple string:
>> "d" == "d"
=> true
>> "d".object_id == "d".object_id
=> false
So It is possible that #mail and #user.emails.last are different objects in memory, but return true when using == method.
You can confirm that there's nothing wrong with your expectation like this:
expect(#user.emails.last).to receive(:send) # should pass now
#user.emails.last.send
You didn't share much code for context, so it's not clear what a "good" solution in your case could be. But I hope this will point you in the right direction.
In my case I'm able to work around this by returning the Email instance (#user.emails.last) from the send_email method and ensuring that is the same object as the #email object in the spec. E.g.,:
# The `send_email` method calls `#user.emails.last.send` and returns `#user.emails.last`
email = #user.send_email
expect(email).to eq(#email) # passes!

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

how to correctly use stub for testing in rspec

So, I have a method in a class as follow:
def installation_backlog
Api::Dashboards::InstallationsBacklog.new(operational_district_id, officer_id).backlog_tasks
end
And I want to spec it. So, I just wrote an RSpec test to test it as follow:
it "should call a new instance of InstallationsBacklog with the backlog_tasks method" do
expect_any_instance_of(Api::Dashboards::InstallationsBacklog).to receive(:backlog_tasks)
#installation_officer.installation_backlog # #installation_officer is a new instance of the container class.
end
And this is working.
However, I began to wonder if this was a correct way of doing it. Like: am I sure that even if I stub the wrong ( probably inexistent ) method, and test for it, will it pass or fail?
I tried it, it passed
So, if later, the method name gets changed, there is no way for this test to detect that.
So, here is the question: How can I be sure that a RSpec stubbed method is actually existent in a code?
Here's how I'd set it up. Might help..
let(:backlog) {
double('Backlog', backlog_tasks: [])
}
before do
allow(Api::Dashboards::InstallationsBacklog).to receive(:new).
and_return(backlog)
end
it 'instantiates InstallationBacklog' do
expect(Api::Dashboards::InstallationBacklog).to receive(:new).
with(operational_district_id, officer_id)
#installation_officer.installation_backlog
end
it 'calls backlog_tasks on instance of InstallationBacklog' do
expect(backlog).to receive(:backlog_tasks)
#installation_officer.installation_backlog
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

Resources