Search code examples
ruby-on-railsrspecdevisecontrollerrspec-rails

How to write an Rspec controller test with authentication?


I have two models:

# app/models/user.rb
class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

  has_many :teams, dependent: :destroy
end

and

# app/models/team.rb
class Team < ActiveRecord::Base
  belongs_to :user

  validates_presence_of :user
end

I am trying to test my TeamsController using Rspec and factory_girl_rails.

Before I can create a new Team I need an authenticated User.

I created a :user factory:

FactoryGirl.define do
  factory :user do
    first_name   "John"
    last_name    "Doe"
    email        {|n| "email#{n}@email.com" }
    mobile_phone "1235551234"
    company_name "Widgets Inc."
    password     "password"
  end
end

Here are the relevant parts of teams_controller_spec.rb:

require 'rails_helper'

RSpec.describe TeamsController, type: :controller do

  # This should return the minimal set of values that should be in the session
  # in order to pass any filters (e.g. authentication) defined in
  # TeamsController. Be sure to keep this updated too.
  let(:valid_session) { {} }

  describe "GET #index" do
    it "assigns all teams as @teams" do
      user = FactoryGirl.create(:user)

      team = Team.create!(name: "New Team Name", user: user)

      get :index, {}, valid_session
      expect(assigns(:teams)).to eq([team])
    end
  end
end

The test is failing:

1) TeamsController GET #index assigns all teams as @teams
     Failure/Error: get :index, {}, valid_session
     NoMethodError:
       undefined method `authenticate' for nil:NilClass

I don't understand how I need to populate :valid_session so that the test will pass. I thought I'd have to explicitly call an authenticate method but that might not be true. I'm trying to test the Team controller... not User authentication.

Any advice would be much appreciated.


Solution

  • I'd do this in your rails_helper:

    module ControllerMacros    
      def sign_me_in
        before :each do
          @request.env['devise.mapping'] = Devise.mappings[:user]
          @current_user = FactoryGirl.create(:user)
          sign_in :user, @current_user
        end 
      end 
    end
    
    Rspec.configure do |config|
        #other rspec stuff
        config.include FactoryGirl::Syntax::Methods
        config.extend ControllerMacros, type: :controller
        config.include Devise::Test::ControllerHelpers, type: :controller
     end
    

    Then in your controller spec, (provided you're requiring your rails_helper) you can just to sign_me_in whenever you want to be signed in and not bother about the valid_session:

    RSpec.describe TeamsController, type: :controller do
      sign_me_in
      #etc...
    end
    

    However in your specific case you want to know who you're signed in as, so you can do this:

    RSpec.describe TeamsController, type: :controller do
      describe "GET #index" do
        it "assigns all teams as @teams" do
          user = FactoryGirl.create(:user)
          team = Team.create!(name: "New Team Name", user: user)
          @request.env['devise.mapping'] = Devise.mappings[:user]
          sign_in :user, user
          get :index
          expect(assigns(:teams)).to eq([team])
        end
      end
    end
    

    The devise mappings line may not be required in your case, but can't say without inspecting your full app.