-
Notifications
You must be signed in to change notification settings - Fork 5.5k
How To: Test controllers with Rails 3 and 4 (and RSpec)
First, be sure to speed up your tests!
To sign in as admin for a given test case, just do:
class SomeControllerTest < ActionController::TestCase
include Devise::TestHelpers
def setup
@request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin)
end
endNote: If you are using the confirmable module, you should set a confirmed_at date inside the Factory or call
confirm! before sign_in.
Here is the basics to prepare inside your Factory:
FactoryGirl.define do
factory :account do
email { Faker::Internet.email }
password "password"
password_confirmation "password"
confirmed_at Date.today
end
endController specs won't work out of the box if you're using any of devise's utility methods.
As of rspec-rails-2.0.0 and devise-1.1, the best way to put devise in your specs is simply to add the following into spec_helper:
require 'devise'
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
endNote: If this include is done before your require 'rspec/rails', then move this include to spec/rails_helper.rb
You can also write to controller_macros_spec.rb file inside spec/support which contains the following:
module ControllerMacros
def login_admin
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:admin]
sign_in FactoryGirl.create(:admin) # Using factory girl as an example
end
end
def login_user
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:user]
user = FactoryGirl.create(:user)
user.confirm! # or set a confirmed_at inside the factory. Only necessary if you are using the "confirmable" module
sign_in user
end
end
endNote: If your admin factory is nested on your user factory, you'll need to call sign_in like this:
def login_admin
before(:each) do
@request.env["devise.mapping"] = Devise.mappings[:admin]
admin = FactoryGirl.create(:admin)
sign_in :user, admin # sign_in(scope, resource)
end
endThen in spec/spec_helper.rb or spec/support/devise.rb:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
endSo now in your controller specs, you can now do:
describe MyController do
login_admin
it "should have a current_user" do
# note the fact that you should remove the "validate_session" parameter if this was a scaffold-generated controller
subject.current_user.should_not be_nil
end
it "should get index" do
# Note, rails 3.x scaffolding may add lines like get :index, {}, valid_session
# the valid_session overrides the devise login. Remove the valid_session from your specs
get 'index'
response.should be_success
end
endEvery time you want to unit test a devise controller, you need to tell Devise which mapping to use. We need that because ActionController::TestCase and spec/controllers bypass the router and it is the router that tells Devise which resource is currently being accessed, you can do that with:
@request.env["devise.mapping"] = Devise.mappings[:admin]If you choose to authenticate in routes.rb, you lose the ability to test your routes via assert_routing (which combines assert_recognizes and assert_generates, so you lose them also). It's a limitation in Rails: Rack runs first and checks your routing information but since functional/controller tests run at the controller level, you cannot provide authentication information to Rack which means request.env['warden'] is nil and Devise generates one of the following errors:
NoMethodError: undefined method 'authenticate!' for nil:NilClass
NoMethodError: undefined method 'authenticate?' for nil:NilClass
The solution is to test authenticated routes in the controller tests. To do this, stub out your authentication methods for the controller test, as described here: How-To: Stub authentication in controller specs
If you're using the default Rspec scaffold generator, the generated controller specs pass along session parameters:
get :index, {}, valid_session
These are overwriting the session variables that Devise's helpers set to sign in with Warden. The simplest solution is to remove them:
get :index, {}
Alternatively, you could set the Warden session information in them manually, instead of using Devise's helpers.