Working with request specs for apps secured using Devise can be tricky. I've recently been using the following approach to authenticate mock users for testing.
The problem is that if you're using Devise within your application for user authentication you'll be securing down your controllers using a before filter.
class InvoicesController < ApplicationController # Authenticate all requests. before_filter :authenticate_user! def index # Magic stuffs. end end
Once you add the filter to your controller you'll notice all of your request and controller specs begin to fail, as they're not being run by an authenticated user.
To solve this, we need to look at a way of authenticating a user for the purposes of testing.
Mocking A User With FactoryGirl
Before we can authenticate our user for the tests, we need to mock the user record. In my case I use FactoryGirl for generating my test objects, rather than fixtures.
Start by defining a simple user within the factory
FactoryGirl.define do # Define a basic devise user. factory :user do email "email@example.com" password "example" password_confirmation "example" end end
Now that we have our user defined, we can look at the process of authenticating them ready for testing.
I'm going to create this as a support file, which will be loaded in to the spec framework when it's initialized, and we can then call upon it when we wish.
# This support package contains modules for authenticaiting # devise users for request specs. # This module authenticates users for request specs.# module ValidUserRequestHelper # Define a method which signs in as a valid user. def sign_in_as_a_valid_user # ASk factory girl to generate a valid user for us. @user ||= FactoryGirl.create :user # We action the login request using the parameters before we begin. # The login requests will match these to the user we just created in the factory, and authenticate us. post_via_redirect user_session_path, 'user[email]' => @user.email, 'user[password]' => @user.password end end # Configure these to modules as helpers in the appropriate tests. RSpec.configure do |config| # Include the help for the request specs. config.include ValidUserRequestHelper, :type => :request end
Now that we have our factory for our user, and the authentication helpers all defined we're ready to start integrating them within our request specs.
Integrating With Your Specs
We have two options for this, and which best suits you will depend on whether you want to test for behaviour both when a user is authenticated and also when they are not - I tend to only worry about behaviour for when the user is authenticated.
To authenticate a user for all tests within a spec set than you can call our helper method within a before() method, which get's invoked, and therefore authenticates our user before each test is run.
# Import the specs helper. require 'spec_helper' # Request specs for the invoices controller. describe "Invoices" do # Runs before each test. before do # Sign in as a user. sign_in_as_a_valid_user end describe "GET /invoices" do it "Gives is the expected status code." do # Invoke the request we're testing. get invoices_path # Ensure we get the expected response code. response.status.should be(200) end end end
The alternative option is to invoke the 'signinasavalid_user' helper method from within each test individualy, which gives you the more fine grain testing of behaviour based on authenticity of the user.
# Import the specs helper. require 'spec_helper' # Request specs for the invoices controller. describe "Invoices" do describe "GET /invoices" do it "Gives is the expected status code when authenticated." do # Sign in as a user. sign_in_as_a_valid_user # Invoke the request we're testing. get invoices_path # Ensure we get the expected response code. response.status.should be(200) end it "Redirects us when not authenticated." do # Invoke the request we're testing. get invoices_path # Ensure we get the expected response code. response.status.should be(302) end end end
A very similar process to this can be used for authenticating users for Controller specs, but the way in which we authenticate the user differs very slightly - I'll expand upon this in a follow-up post some time in the near future.