Search code examples
ruby-on-railsrspecdevisewarden

How to sign users in and out for testing with Rspec + Devise + FactoryGirl


I'm new to Rspec, but I've managed to put together a working (at least in my tests so far) setup that lets me test various behavior in logged in/logged out states using FactoryGirl + Devise and Warden helpers. The flow follows these basic steps:

  1. Factory girl defines a generic user
  2. Each test block that requires a login uses a before(:each) hook to sign users in
  3. rails_helper config tears down user login after each test with an after :each hook

I've looked a lot of code examples to get this working, but I've yet to find a complete round trip and although I know this is working in my setup, I am wondering if it's the proper way, specifically, am I am duplicating anything unnecessarily (like user sign in tear downs) or creating future unexpected behavior.

Here is the relevant code for each step and a sample test:

spec/factories.rb

FactoryGirl.define do

  factory :user do
    sequence(:name)       { |n| "person #{n}"}
    sequence(:email)      { |n| "person#{n}@example.com" }
    password              'foobar'
    password_confirmation 'foobar'
    confirmed_at          Time.now
    sequence(:id)         { |n| n }
  end
end

spec/rails_helper.rb

...
  # Add login/logout helpers from Devise
  config.include Devise::Test::ControllerHelpers, type: :controller
  config.include Devise::Test::ControllerHelpers, type: :view

  # Include Warden test helpers specifically for login/logout
  config.include Warden::Test::Helpers

  # Add capybara DSL
  config.include Capybara::DSL

  # Tear down signed in user after each test
  config.after :each do
    Warden.test_reset!
  end

spec/views/static_pages (sample test)

RSpec.describe 'static_pages home, signed in', type: :view do
  before(:each) do
    @user = build(:user)
    login_as(@user)
  end

  it 'should display the correct links when signed in' do

    visit root_path

    # links which persist in both states
    expect(page).to have_link('Site Title', href: root_path, count: 1)

    # links which drop out after login
    expect(page).not_to have_link('Login', href: new_user_session_path)
    expect(page).not_to have_link('Join', href: signup_path)

    # links which are added after login
    expect(page).to have_link('Add Item', href: new_item_path)
    expect(page).to have_link('My Items', href: myitems_path)
  end
end

Solution

  • Your setup is totally OK. One thing as @Greg Tarsa said is that you may want to perform such kind of tests at feature level. Another thing from me is that one should use one single spec to test one single thing e.g. it should be single (or several) expect in it block. But it's not strict rule - it's up to you to decide.

    And I made some refactoring of your setup with previous tips and feature-style syntax. Maybe it would be useful:

      background do
        @user = build(:user)
        login_as(@user)
        visit root_path
      end
    
      scenario "links persist in both states" do
        expect(page).to have_link('Site Title', href: root_path, count: 1)
      end
    
      scenario "links dropped out after login" do
        expect(page).not_to have_link('Login', href: new_user_session_path)
        expect(page).not_to have_link('Join', href: signup_path)
      end
    
      scenario "links added after login" do
        expect(page).to have_link('Add Item', href: new_item_path)
        expect(page).to have_link('My Items', href: myitems_path)
      end