Rails: problem setting expectations on mock model in RSpec - ruby-on-rails

I am trying to set expectations on a mocked ActiveRecord model. I have created the following example, which should pass based on the documentation I can find.
it "should pass given the correct expectations" do
payment = mock_model(Payment)
payment.should_receive(:membership_id).with(12)
payment.membership_id = 12
end
It is failing with the error "...Mock 'Payment_1004' received unexpected message :membership_id= with (12)"
I realize I am testing the mocking framework, I am just trying to understand how to setup expectations.

You're setting the expectation on the wrong method name - :membership_id is the "getter", :membership_id= is the "setter". The correct line would be:
payment.should_receive(:membership_id=).with(12)

Another useful "out" here -- if one doesn't care about the id key -- is to do something like the following:
mock_model(Payment,:[]= => nil, :save=> nil)
...or maybe just
mock_model(Payment,:[]= => nil)
Lille

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!

conditional match on rails rspec

I want to write a test on helper method which fetches data from external service based on an id. So there is an uncertainty whether the value will be returned or nil. But if value is returned, id of returned value must be equal to given id. Is there a way to achieve this?
expect(record).to be_nil.or expect(record.id).to eq(deal.user_id)
however it seems the or condition does not work the way I think. I am new to RoR. Might be missing any obvious way to do it.
You can write ruby code in specs too:
if record != nil
expect(record.id).to eq(deal.user_id)
end
or combine matchers:
expect(record).to be_nil.or(eq(deal.user_id))

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.

Rails/Rspec Stubs: Specifying order & so forth

I'm comfortable creating basic stubs, but a little confused about how to specify things such as order (and reversing it), etc.
To give a concrete example, here is a line in my controller that I'm trying to test:
#courses = Course.order('created_at').reverse
In my controller spec, the (obviously failing) stub is:
Course.stub(:all) { [mock_course] }
...and the rspec error:
Failure/Error: assigns(:courses).should eq([mock_course])
expected [#<Course:0x818614e8 #name="Course_1001">]
got [#<Course id: 2, name: "Second test course", price: #<BigDecimal:1030e73c0,'0.4995E2',18(18)>]
Despite being an inaccurate test (not testing ordering), i would have guessed the spec would pass. It doesn't - it's pulling from the database, not the mock. Soo...I guess what I'm asking is, how do I stub Course.order('created_at').reverse ?
Many thanks...
You're assuming that ActiveRecord will call Course.all at some point, which may not be the case.
Try this:
Course.stub_chain(:order, :reverse) { [mock_course] }
To avoid having to do chained stubbing, you could move the ActiveRecord code into your model, so the controller would have something simpler such as:
Course.all_by_newest_first

Rails assert that form is valid

What's the best practices way to test that a model is valid in rails?
For example, if I have a User model that validates the uniqueness of an email_address property, how do I check that posting the form returned an error (or better yet, specifically returned an error for that field).
I feel like this should be something obvious, but as I'm quickly finding out, I still don't quite have the vocabulary required to effectively google ruby questions.
The easiest way would probably be:
class UserEmailAddressDuplicateTest < ActiveSupport::TestCase
def setup
#email = "test#example.org"
#user1, #user2 = User.create(:email => #email), User.new(:email => #email)
end
def test_user_should_not_be_valid_given_duplicate_email_addresses
assert !#user2.valid?
end
def test_user_should_produce_error_for_duplicate_email_address
# Test for the default error message.
assert_equal "has already been taken", #user2.errors.on(:email)
end
end
Of course it's possible that you don't want to create a separate test case for this behaviour, in which case you could duplicate the logic in the setup method and include it in both tests (or put it in a private method).
Alternatively you could store the first (reference) user in a fixture such as fixtures/users.yml, and simply instantiate a new user with a duplicate address in each test.
Refactor as you see fit!
http://thoughtbot.com/projects/shoulda/
Shoulda includes macros for testing things like validators along with many other things. Worth checking out for TDD.
errors.on is what you want
http://api.rubyonrails.org/classes/ActiveRecord/Errors.html#M002496
#obj.errors.on(:email) will return nil if field is valid, and the error messages either in a String or Array of Strings if there are one or more errors.
Testing the model via unit tests is, of course, step one. However, that doesn't necessarily guarantee that the user will get the feedback they need.
Section 4 of the Rails Guide on Testing has a lot of good information on functional testing (i.e. testing controllers and views). You have a couple of basic options here: check that the flash has a message in it about the error, or use assert_select to find the actual HTML elements that should have been generated in case of an error. The latter is really the only way to test that the user will actually get the message.

Resources