How to not execute a stubbed method in RSpec - ruby-on-rails

I am testing that a controller calls a certain method in a module that it includes. I don't want the method to actually execute, just to verify that it is called. What I have right now is:
it 'calls create_show_and_tickets' do
sign_in create :admin
expect(subject).to receive(:create_show_and_tickets)
post :create, show: valid_attributes
end
In the test I am not putting proper parameters in, so it is throwing an error when I tries to iterate over an object that doesn't exist. Is there a way to make sure that create_show_and_tickets does not execute?

Is there a way to make sure that create_show_and_tickets does not execute?
You have done just that by calling
expect(subject).to receive(:create_show_and_tickets)
... before the post. allow would have the same effect. In either case the underlying code is replaced by your mock or stub, and will not get called.
You can put a debugging statement within create_show_and_tickets to confirm this.

Related

Rspec: Test if instance method was called on specific record with ActiveRecord

I'm looking to write a controller spec which tests if a method was called on an instance of a model class for ActiveRecord.
For example, there is a model Post and I want to check if the post with the id 55 had the method foobar called on it.
Some ways that almost work:
expect_any_instance_of(Post).to receive(:foobar)
This almost works but it can not check which post the method was called on
using double
This would normally work but in the controller spec, only ids are passed over so I have no way of inserting the double, short of mocking the response from activerecord find
Does rspec provide any tools to check a method was called on a specific model instance?
It is possible but requires much more mocking, and this means that you're testing the implementation details of a controller which is an antipattern.
Assuming your controller is this:
def action(id)
Post.find(params[:id]).foobar
end
You'd need to mock sth like this (I'll ignore RSpec's good practices like having vars in let's, you can easily extract it later when you have a working example):
mock = instance_double(Post)
expect(Post).to receive(:find).with(55).and_return(mock)
expect(mock).to receive(:foobar)
# trigger the action here
But as the comments already stated, it's probably cleaner to test side effects. If your action deletes a Post, check that the post has been deleted
expect { trigger_action params: {id: 55} }.to change(Post, :count).by(-1)
This makes your code less brittle (you can refactor the internals, and the specs for the controller stay green - meaning your app still works as described in those specs).

Rspec: How to use expect to receive with a resource which does not exist yet?

In an action called via a post request I'm creating a resource call RequestOffer and send an email with ActionMailer using the created resource as a parameter:
#request_offer = RequestOffer.new(request_offer_params)
if #request_offer.save
RequestOfferMailer.email_team(#request_offer).deliver_later
end
When my controller spec, I want to test that my RequestOfferMailer is called using the method email_team with the resource #request_offer as a parameter.
When I want to user expect(XXX).to receive(YYY).with(ZZZ), the only way I found was to declare my expectation before making the POST request. However, ZZZ is created by this POST request, so I have no way to set my expectation before.
# Set expectation first
message_delivery = instance_double(ActionMailer::MessageDelivery)
# ZZZ used in .with() does not exist yet, so it won't work
expect(RequestOfferMailer).to receive(:email_team).with(ZZZ).and_return(message_delivery)
expect(message_delivery).to receive(:deliver_later)
# Make POST request that will create ZZZ
post :create, params
Any idea how to solve this problem?
If this is a functional test then I would isolate the controller test from the DB. You can do this by using instance_doubles and let statements. Here's an example that you may like to extend for your purposes
describe '/request_offers [POST]' do
let(:request_offer) { instance_double(RequestOffer, save: true) }
before do
allow(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
end
it 'should instantiate a RequestOffer with the params' do
expect(RequestOffer).to receive(:new).
with(...params...).
and_return(request_offer)
post '/request_offers', {...}
end
it 'should email the request offer via RequestOfferMailer' do
mailer = instance_double(ActionMailer::MessageDelivery)
expect(RequestOfferMailer).to receive(:email_team).
with(request_offer).and_return(mailer)
post '/request_offers', {...}
end
end
The key to this is using 'let' to declare an instance double of the model that you intend to create. By setting expectations on the class you can inject your instance double into the test and isolate from the DB. Note that the 'allow' call in the before block is there to serve the later specs that set expectations on the mailer object; the 'expect' call in the first test will still be able to make assertions about the call.
Would it be enough to make sure the argument is an instance of RequestOffer? Then you could use the instance_of matcher. For example:
expect(RequestOfferMailer).to receive(:email_team).with(instance_of(RequestOffer)).and_return(message_delivery)
I found this option in the Rspec 3.0 docs: https://relishapp.com/rspec/rspec-mocks/v/3-0/docs/setting-constraints/matching-arguments
The last argument of the with method is a block. You can open up the arguments and do anything you like there.
expect(RequestOfferMailer)
.to receive(:email_team)
.with(instance_of(RequestOffer)) do |request_offer|
expect(request_offer.total).to eq(100) # As one example of what you can to in this block
end.and_return(message_delivery)
You can also set the instance_of matcher to be anything if you're not even sure what object type you're expecting.

mocking controller methods in rspec

(This question is similar to Ruby on Rails Method Mocks in the Controller, but that was using the old stub syntax, and besides, that didn't receive a working answer.)
short form
I want to test my controller code separate from my model code. Shouldn't the rspec code:
expect(real_time_device).to receive(:sync_readings)
verify that RealTimeDevice#sync_readings gets called, but inhibit the actual call?
details
My controller has a #refresh method that calls RealTimeDevice#sync_readings:
# app/controllers/real_time_devices_controller.rb
class RealTimeDevicesController < ApplicationController
before_action :set_real_time_device, only: [:show, :refresh]
<snip>
def refresh
#real_time_device.sync_readings
redirect_to :back
end
<snip>
end
In my controller tests, I want to verify that (a) that #real_time_device is being set up and (b) the #sync_reading model method is getting called (but I don't want to invoke the model method itself since that's covered by the model unit tests).
Here's my controller_spec code that doesn't work:
# file: spec/controllers/real_time_devices_controller_spec.rb
require 'rails_helper'
<snip>
describe "PUT refresh" do
it "assigns the requested real_time_device as #real_time_device" do
real_time_device = RealTimeDevice.create! valid_attributes
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => real_time_device.to_param}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
<snip>
When I run the test, the actual RealTimeDevice#sync_readings method is getting called, i.e., it's trying to call code in my model. I thought the line:
expect(real_time_device).to receive(:sync_readings)
was necessary and sufficient to stub the method and verify that it got called. My suspicion is that it needs to be a double. But I can't see how to write the test using a double either.
What am I missing?
You're setting an expectation on a specific instance of RealTimeDevice. The controller fetches the record from the database, but in your controller, it's using another instance of RealTimeDevice, not the actual object you set the expectation on.
There are two solutions to this problem.
The Quick and Dirty
You can set an expectation on any instance of RealTimeDevice:
expect_any_instance_of(RealTimeDevice).to receive(:sync_readings)
Note that this is not the best way to write your spec. After all, this doesn't guarantee that your controller fetches the right record from the database.
The Mocking Approach
The second solution involves a bit more work, but will cause your controller to be tested in isolation (which it is not really if it's fetching actual database records):
describe 'PUT refresh' do
let(:real_time_device) { instance_double(RealTimeDevice) }
it 'assigns the requested real_time_device as #real_time_device' do
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
expect(real_time_device).to receive(:sync_readings)
put :refresh, {:id => '1'}, valid_session
expect(assigns(:real_time_device)).to eq(real_time_device)
end
end
Quite some things have changed. Here's what happens:
let(:real_time_device) { instance_double(RealTimeDevice) }
Always prefer using let in your specs rather than creating local variables or instance variables. let allows you to lazy evaluate the object, it's not created before your spec requires it.
expect(RealTimeDevice).to receive(:find).with('1').and_return(real_time_device)
The database lookup has been stubbed. We're telling rSpec to make sure that the controller fetches the correct record from the database. The important part is that the very instance of the test double created in the spec is being returned here.
expect(real_time_device).to receive(:sync_readings)
Since the controller is now using the test double rather than an actual record, you can set expectations on the test double itself.
I've used rSpec 3's instance_double, which verifies the sync_readings method is actually implemented by the underlying type. This prevents specs from passing when a method would be missing. Read more about verifying doubles in the rSpec documentation.
Note that it's not required at all to use a test double over an actual ActiveRecord object, but it does make the spec much faster. The controller is now also tested in complete isolation.

Dynamic method call in routes spec

I am testing simple get requests for my routes using rspec in my Rails 3.2 application. Since all are get requests, and all just have different action names which are similar to the views' names, it would be really repetitive to manually write a different test for each get request.
Instead, I wanted to come up with something like this:
%(action_1 action_2 action_3 action_4).each do |action|
it "routes to the #{action} page" do
get("liver_diseases#{action}_path").should route_to("liver_diseases##{action}")
end
end
It fails at this pseudocode: get("liver_diseases_#{action}_path")
So what I need to do is a dynamic method call - but for what I have found out, that would involve .send(:method_name), for which I need to know the class name. And I couldn't find that.
What do I need to do for this method call to work?
that would involve .send(:method_name), for which I need to know the
class name
When the receiver is missing, it's always self. In the context of a controller example, self should be a controller instance. So you should be able to get that path with:
send "liver_diseases_#{action}_path"
which should be equivalent to:
controller.send "liver_diseases_#{action}_path"

Using rspec to test ActionMailer with should_receive

I have an RSpec test like this:
it "should ..." do
# mailer = mock
# mailer.should_receive(:deliver)
Mailer.should_receive(:notification_to_sender)#.and_return(mailer)
visit transactions_path
expect do
page.should_not have_css("table#transactions_list tbody tr")
find('#some_button').click
page.should have_css("table#transactions_list tbody tr", :count => 1)
end.to change{Transaction.count}.by(1)
end
If I remove the commented pieces at the top, the test passes. But with the commented sections in place (how I'd expect to write it) the test fails.
I got the commented pieces off some of googling around the net, but I don't really understand what it's doing or why this fixes it. It seems like there should be a cleaner way to test emails without this.
Can anyone shed some light? Thanks!
I'm using rails 3 and rspec-rails 2.10.1
I think you want an instance of Mailer to receive notification_to_sender not the class. From the Rails API
You never instantiate your mailer class. Rather, your delivery instance methods are automatically wrapped in class methods that start with the word deliver_ followed by the name of the mailer method that you would like to deliver. The signup_notification method defined above is delivered by invoking Notifier.deliver_signup_notification.
Therefore I would use
Mailer.any_instance.should_receive(:notification_to_sender)
Also, if you need to get the last delivered message, use
ActionMailer::Base.deliveries.last
I think that should solve your problem.
You're likely calling Mailer.notification_to_sender.deliver in your controller, or better yet, a background job. I'm guessing notification_to_sender probably takes a parameter as well.
Anyways, when you call the notification_to_sender method on Mailer you're getting back an instance of Mail::Message that has the deliver method on it. If you were simply doing Mailer.notification_to_sender without also calling deliver, you could run what you have there with the comments and all would be fine. I would guess you're also calling deliver though.
In that case your failure message would be something like
NoMethodError:
undefined method `deliver' for nil:NilClass
That is because nil is Ruby's default return value much of the time, which Rails also inherits. Without specifying the mailer = mock and .and_return(mailer) parts, when the controller executes in context of the test then notification_to_sender will return nil and the controller will try to call deliver on that nil object.
The solution you have commented out is to mock out notification_to_sender's return value (normally Mail::Message) and then expect that deliver method to be called on it.

Resources