I am having trouble testing an external instance variable with Rspec. While there are some similar questions on StackOverflow, I haven't been able to find a solution for testing it in a GET request (since this is for an API).
I have an ApiController method that does something along these lines:
class ApiController < ApplicationController
...
def auth(token)
#user = User.find_by_token(token)
...
end
Then, I have a request spec that looks something like this:
describe 'GET /v1/friends' do
it 'returns friends' do
ApiController.any_instance.stub(:auth)
get "v1/friends"
...
end
end
I get an error that says undefined method XXX for nil:NilClass that stems from the fact that the #users instance variable isn't being passed into the GET request.
I have tried using assigns() and that doesn't seem to work, while not stubbing the auth class isn't an option for a different reason. And, simply setting the instance variable doesn't seem to work, because I've read that Rspec assumes that it's its own variable rather than one used in the test (?).
Is there any way to set the #user instance variable to a fake user when running the GET request?
Thanks in advance for any help!
I ended up refactoring quite a bit of other code to run the authentication method that creates the instance variable in the actual controller. I don't believe there is a way to set a variable in Rspec for use in a GET request.
When using an integration test to test authenticate_with_http_token, you'll need to include it like so in the GET request:
get '/v1/path-for-api', nil, authorize: 'Token token=#{user.token}'
From there, the variable was properly set now and I was able to test the requests.
Related
I have an ActiveRecord object User. In the app I am making I am using a single sign on gem, but I need to save some of the user data in our databse. My ApplicationController has this code:
def create_user
User.create(name: current_user['name'], email: current_user['email'], company_id: current_user['id'])
end
I need an RSpec test that mocks out the actual create call. I have tried
allow_any_instance_of(User).to receive(:create).with(any_args).and_return(user)
which returns an error saying "User does not implement create".
jvillian is correct that the problem is that create is implemented by User, not by an instance of User. The simple fix is just to stub the method directly on User (i.e. use allow instead of allow_any_instance_of):
allow(User).to receive(:create).with(any_args).and_return(user)
Also, .with(any_args) is a no-op, so this is equivalent:
allow(User).to receive(:create).and_return(user)
I'm thinking that allow_any_instance_of is expecting an instance of User to implement create. create, however, is a class method. So, I believe the error messages is saying that instances of User don't implement create.
I'd suggest seeing if class_double works for your use case. Check out Rspec Mocks and this SO post from Myron Marston.
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.
(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.
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"
I have a feeling I'm just doing something wrong syntactically but it's surprisingly difficult to Google "GET" so I was hoping someone here might know the answer to this.
I'm trying to test a Rails controller from an RSpec test. I'm following an example I found here - http://www.elevatedrails.com/articles/2007/09/10/testing-controllers-with-rspec/ but I'm stuck on the actual execution of the method I'm testing.
I'm doing a GET request where as the post above does a POST. I'm passing in 2 parameters manufacturer and model.
My URL will ideally look something like http://mysite.com/Products/index/Manufacturer/ModelName
I can't figure out the syntax for the get request call in the rest. Right now I have
get :index, :manufacturer=>#manufacturer, :modelName=>#modelName
and I get back
ArgumentError in 'ProductController Find a valid product should retrieve the product'
wrong number of arguments (0 for 2)
Any thoughts?
edit: It should be noted #manufacturer and #modelName are defined in before(:each)
As i suspected this was me being green to rails programming.
I was defining the controller method as
def index(manufacturer, modelName)
When really i needed to use the params hash to access the attributes. I then had to define a custom route as id is the only parameter expected to be passed into a controller method by default.
once i did that i changed the spec to read
get :index, {:manufacturer=>#manufacturer, :modelName=>#modelName}
and it worked.
Thanks for the comments everyone.