Testing controllers in Rails 3 with Devise and RSpec
I am tr开发者_如何学运维ying 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_object
essentially tells the mock to accept all messages that aren't stubbed and just return self. So when you call
current_user.raw_data_sets.build(params[:raw_data_set])
which would normally return a new RawDataSet
associated to current_user
, instead you get current_user
again.
So when you try to call redirect, passing in @raw_data_set
, you're passing it the mock instead of a RawDataSet
instance, thus the errant call to user_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_users
becomes (for instance):
def mock_users(stubs={})
@user = User.create(stubs)
end
Now current_user
will 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?
If you need to do something specific for example change the behavior of sign-in, you can inherit from devise controllers and overwrite them. you can follow the instruction from here https://github.com/plataformatec/devise the configuring controllers.
精彩评论