I am trying to test my controller’s create action. I am using Devise for authentication, and have used the before_filter method to limit access to the create action to logged-in users. Here is the relevant part of my controller.
class RawDataSetsController < ApplicationController
before_filter :authenticate_user!, :except => [:show, :index, :download]
def create
@raw_data_set = current_user.raw_data_sets.build(params[:raw_data_set])
if @raw_data_set.save
redirect_to @raw_data_set
else
render "new"
end
end
end
In my test case I want to make sure that a logged-in user can create a RawDataSet. I think that I have mocked up an authenticated user according to the instructions on this blog post.
require 'spec_helper'
describe RawDataSetsController do
include Devise::TestHelpers
def mock_users(stubs={})
@user ||= mock_model(User, stubs).as_null_object
end
def log_in_test_user
attr = { :username => "Foobar", :email => "doineedit@foobar.com" }
#mock up an authentication in warden as per http://www.michaelharrison.ws/weblog/?p=349
request.env['warden'] = mock(Warden, :authenticate => mock_users(attr),
:authenticate! => mock_users(attr),
:authenticate? => mock_users(attr))
end
before do
@rds = Factory(:raw_data_set)
end
describe "POST 'create'" do
before do
@attr = {
:organism_name => "Beef Jerky",
:mass_spec_type => "My Stomach",
}
end
describe "logged in user" do
it "should create a raw_data_set when the user is logged in" do
log_in_test_user
lambda do
post :create, :raw_data_set => @attr
end.should change(RawDataSet, :count).by(1)
end
end
end
end
Running this test case causes the following error:
1) RawDataSetsController POST 'create' logged in user should create a raw_data_set when the user is logged in
Failure/Error: post :create, :raw_data_set => @attr
undefined method `user_url' for #<RawDataSetsController:0x0000010175af88>
# ./app/controllers/raw_data_sets_controller.rb:7:in `create'
I am baffled by this error. Upon more inspection, @raw_data_set is not an instance of the RawDataSet model class, but a user? This is what it looks like when I p @raw_data_set
#<User:0x807a06a4 @name="User_1002">
What the devil is going on? What am I doing wrong? How can I test the create action on my controller with a an authenticated user?
EDIT (totally wrong first attempt removed)
Calling
as_null_objectessentially tells the mock to accept all messages that aren’t stubbed and just return self. So when you callcurrent_user.raw_data_sets.build(params[:raw_data_set])which would normally return a new
RawDataSetassociated tocurrent_user, instead you getcurrent_useragain.So when you try to call redirect, passing in
@raw_data_set, you’re passing it the mock instead of aRawDataSetinstance, thus the errant call touser_url.I think the way to handle this is just use a real User (or a Factory) and stub out the Devise methods on it. So your
mock_usersbecomes (for instance):Now
current_userwill actually do the build and save through the association.Purists will tell you to mock and stub out everything until you’re blue in the face. Screw that — you’ve got better things to do. 🙂
The other way to approach this is to test that the build message is received without checking whether the save succeeded. Presumably your model tests will verify that saving through the association works — why test again in the controller?