rails minitest stubbing to assert alternate method - ruby-on-rails

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.

Related

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

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 Mocking and return value

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.

How to test uniqueness of Coupon/Promo-Codes?

I have a Model PromoCode which has a .generate! method, that calls .generate which generates a String using SecureRandom.hex(5) and saves it to the database:
class PromoCode < ActiveRecord::Base
class << self
def generate
SecureRandom.hex 5
end
def generate!
return create! code: generate
end
end
end
Now I want to write a spec that test the uniqueness of the generated string. The .generate method should be called as long as a non existent PromoCode has been generated.
I'm not sure how to do this since I can't really stub out the .generate method to return fixed values (because then it would be stuck in an infinite loop).
This is the passing spec for the model so far:
describe PromoCode do
describe ".generate" do
it "should return a string with a length of 10" do
code = PromoCode.generate
code.should be_a String
code.length.should eql 10
end
end
describe ".generate!" do
it "generates and returns a promocode" do
expect {
#promo = PromoCode.generate!
}.to change { PromoCode.count }.from(0).to(1)
#promo.code.should_not be_nil
#promo.code.length.should eql 10
end
it "generates a uniq promocode" do
end
end
end
Any directions appreciated.
Rspec's and_return method allows you to specify multiple return values that will be cycled through
For example you could write
PromoCode.stub(:generate).and_return('badcode1', 'badcode2', 'goodcode')
Which will cause the first call to generate to return 'badcode1', the second 'badcode2' etc... You can then check that the returned promocode was created with the correct code.
If you want to be race condition proof you'll want a database uniqueness constraint, so your code might actually want to be
def generate!
create!(...)
rescue ActiveRecord::RecordNotUnique
retry
end
In your spec you would stub the create! method to raise the first time but return a value the second time
And what about something like: create a PromoCode, save the result, and try to create a new PromoCode with the code of the previous PromoCode object:
it "should reject duplicate promocode" do
#promo = PromoCode.generate!
duplicate_promo = PromoCode.new(:code => #promo.code)
duplicate_promo.should_not be_valid
end
Also, this is model level, I am assuming you have a key in the database that will save you from race conditions...
If you saves the promocode in database you would have added validations there in the model for uniq promocode. So you can test the same in rspec too.
Like this,
it { should validate_uniqueness_of(:promocode) }
This answer is based on your comment:
I need to make sure that generate! generates a code - no matter what,
until a unique code has been generated.
I feel like you might have a hard time testing this correctly. Unit testing "no matter what" situations with indefinite loops can be a bit of a tricky subject.
I'm not sure how to do this since I can't really stub out the .generate method to return fixed values (because then it would be stuck in an infinite loop).
One possibility to consider might be if instead of doing either one or the other, you tried both? (That is, find a way make it return a fixed number under certain circumstances, and eventually trigger the actual random number. An instance variable counter might help; set it to a random number, count it down, when it's greater than zero return the fixed number, or something along those lines). This still doesn't feel like a perfect test, though, or even a very good one for that matter.
It might be worth looking more into means of generating similar strings with high probability of them being unique, and having some mathematical proof of it being that way. I'm not saying this is the most practical idea either, but if you really need to prove (as you're trying to do with tests), it might be the more probably solution.

Resources