Skip to content

How To: Controllers and Views tests with Rails 3 (and rspec)

genexp edited this page Jun 29, 2012 · 20 revisions

First, be sure to speed up your tests!

Controller tests (Test::Unit)

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 call
confirm! before sign_in

Controller specs

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

Mappings

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]

Authenticated routes in Rails 3

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.

Clone this wiki locally