Rspec expect to receive not working for object in array - ruby-on-rails

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!

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.

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

Rails 4: Create object only in a factory method?

Going to simplify a bit here, but assume an app that has Users and UserRecords. A User must have one or more UserRecords. I want to limit the creation of UserRecords to a method in User, namely #create_new_user_record.
In other words, I don't want to allow UserRecord.new or UserRecords.create anywhere else in the application. I need to control the creation of these records, and perform some logic around them (for example, setting the new one current and any others to not current), and I don't want any orphaned UserRecords in the database.
I tried the after_initialize callback and checking if the object is new and raising an error there, but of course I do need to call UserRecord.new in User#create_new_user_record. If I could somehow flag in #create_new_user_record that I am calling new from that method, and pick that up in after_intialize, that would work, but how?
I might be over thinking it. I can certainly create a that method on User, and just 'know' to always call it. But others will eventually work on this app, and I will go away and come back to it as some point.
I suppose I could raise the error and just rescue from it in #create_new_user_record. Then at least, if another develop tries it elsewhere they will find out why I did it when they pursue the error.
Anyway, wondering what the Rails gurus here had to say about it.
super method is what you are looking for. Though you'll need some workaround (maybe simple check for value of option only you know about) to fit your needs
class User < ActiveRecord:Base
def .new(attributes = nil, options = {})
do_your_fancy_stuff
if option[:my_secret_new_method]
super # call AR's .new method and automatically pass all the arguments
end
end
Ok, here's what I did. Feel free to tell me if this is bad idea or, if it's an ok idea, if there's a better way. For what it's worth, this does accomplish my goal.
In the factory method in the User model, I send a custom parameter in the optional options hash defined on the new method in the API. Then I in the UserRecord#new override, I check for this parameter. If it's true, I create and return the object, otherwise I raise in custom error.
In my way of thinking, creating a UserRecord object any other way is an error. And a developer who innocently attempts it would be lead to explanatory comments in the two methods.
One thing that's not clear to me is why I need to leave off the options hash when I call super. Calling super with it causes the ArgumentError I posted in my earlier comment. Calling super without it seems to work fine.
class User < ActiveRecord::Base
...
def create_new_user_record
# do fancy stuff here
user_record = UserRecord.new( { owner_id: self.id, is_current: true }, called_from_factory: true )
user_record.save
end
...
end
class UserRecord < ActiveRecord::Base
...
def UserRecord.new(attributes = nil, options = {})
if options[:called_from_factory] == true
super(attributes)
else
raise UseFactoryError, "You must use factory method (User#create_new_user_record) to create UserRecords"
end
end
...
end

Help with ruby and def respond_to?(method,*args, &block) in library

I have a Gem that deals with images that get modified. I want to modify it to update the old image but, I need to be able to get the object id.
Right now it uses the following code:
def respond_to?(method,*args, &block)
puts ("++++METHOD #{method.to_s} ARGS #{args} BLOCK #{block}")
args.each do |value|
puts ("ARGS #{value}")
end
but I don't know how to get id from something I pass in nor do I know how to pass the id in, I've tried
#asset.image.s_245_245(:asset_id=>#asset.id) with no success. Args returns nothing. What am I doing wrong?
Update: I am currently reading http://www.simonecarletti.com/blog/2009/09/inside-ruby-on-rails-extract_options-from-arrays/
Update: This too returned blank.
Your question is very unclear. How is the method respond_to? related to your problem with object id?
I am guessing that in reality you wanted to override method_missing, because now you do not call respond_to?, or it is not shown in your examples.
If you have not defined such method, calling image.s_245_245 will trigger method_missing (in the image object) with the parameters you used for respond_to?.
There is a rule, which says that if you use method_missing to handle some calls, then you should also modify respond_to?, and make it returning true when asked for the methods handled by method_missing.
As for object ID, there are two possibilities:
Every object in ruby responds to .object_id (which returns an internal identifier of every object)
ActiveRecord objects respond to .id (which is a primary key in the database).
This is just a side-note, because I suppose that if you start experimenting with method_missing instead of respond_to? you will know which one you want.

Resources