-
Notifications
You must be signed in to change notification settings - Fork 0
How To: Controllers and Views tests with Rails 3 (and rspec)
First, be sure to speed up your tests!
To sign up 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
end
Note: If you are using the confirmable module, you should set a
confirmed_at
date inside the Factory or callconfirm!
before sign_in
Controller 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:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
end
I also like to write controller_macros.rb inside spec/support file 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
end
Then in spec_helper:
RSpec.configure do |config|
config.include Devise::TestHelpers, :type => :controller
config.extend ControllerMacros, :type => :controller
end
So now in my controller specs I can now do:
describe MyController do
login_admin
it "should have a current_user" do
# note the fact that I removed 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
end
Every 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 your routes via integration tests. Authenticating your routes has its advantages, this is one of the disadvantages.