Skip to content

Instantly share code, notes, and snippets.

@markandrewj
Last active August 17, 2022 04:30
Show Gist options
  • Save markandrewj/9270d311c3a14b3c3565 to your computer and use it in GitHub Desktop.
Save markandrewj/9270d311c3a14b3c3565 to your computer and use it in GitHub Desktop.
How To Stub authentication in Devise controller specs

https://github.com/plataformatec/devise/wiki/How-To:-Stub-authentication-in-controller-specs

These are instructions for allowing you to use resource test doubles instead of actual ActiveRecord objects for testing controllers where Devise interactions are important. This means there's even less reliance on the database for controller specs, and that means faster tests!

This approach certainly needs some evolution, but it's a start.

To stub out your user for an action that expects an authenticated user, you'll want some code like this (once you've got Devise::TestHelpers loaded):

  user = double('user')
  allow(request.env['warden']).to receive(:authenticate!).and_return(user)
  allow(controller).to receive(:current_user).and_return(user)

And for behaviours where the user is not signed in:

  allow(request.env['warden'])
    .to receive(:authenticate!)
    .and_throw(:warden, {:scope => :user})

If you're paying attention, you'll see there's the scope for the given resource - adapt that as necessary (and it's especially important if you're using more than one resource scope for Devise in your app).

  gem 'rspec-rails', '~> 3.0'

As a more fleshed out example, I have the following in a file I've added to my app at spec/support/controller_helpers.rb:

  module ControllerHelpers
    def sign_in(user = double('user'))
      if user.nil?
        allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => :user})
        allow(controller).to receive(:current_user).and_return(nil)
      else
        allow(request.env['warden']).to receive(:authenticate!).and_return(user)
        allow(controller).to receive(:current_user).and_return(user)
      end
    end
  end

Require the helper in spec_helper.rb:

  require 'support/controller_helpers'
  ...
  RSpec.configure do |config|
    ...
    config.include Devise::TestHelpers, :type => :controller
    config.include ControllerHelpers, :type => :controller
    ...
  end

And then in controller examples we can just call sign_in to sign in as a user, or sign_in nil for examples that have no user signed in. Here's two quick examples:

  it "blocks unauthenticated access" do
    sign_in nil
    
    get :index
    
    expect(response).to redirect_to(new_user_session_path)
  end
  
  it "allows authenticated access" do
    sign_in
    
    get :index
    
    expect(response).to be_success
  end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment