Testing Controllers with Authentication

Fast forward to some prettier routes and views!

$> git checkout controller_auth_testing_start

What's changed in our app?

  • We've added some more routes (/dashboard, /register, /login, /settings, /logout)
  • We've added basic user authentication with the (Devise) gem that allows users to sign up and sign in to our app. Note: we're not going to get in depth about Devise usage, but check out their super extensive docs for more info!

Let's run our tests -- I see one controller test pending!

Add a basic controller test for the dashboard route

describe "GET #dashboard" do
  it "returns a success response" do
    get :dashboard
    expect(response).to be_success
  end
end

Run our tests -- yay! They pass!

But wait -- a bug has been reported!

You just received word that non-authenticated users are able to access the dashboard. What?! Let's investigate.

Reproduce the bug

Boot up your server and take a look at the app -- it's looking prettier than the last time we saw it!

Navigate to the dashboard page without being logged in...

Uh oh. Bug confirmed.

Reproduce in test

We have a bug... and a passing test suite. Let's change that. Something should be failing.

  • Change your dashboard test to fail. We don't want a non-authenticated user to access that page.
describe "GET #dashboard" do
  it "does not return a successful response" do
    get :dashboard
    expect(response).not_to be_success
  end
end

Great now our test fails. Let's make that pass.

  • Open up your application controller (app/controllers/application.rb). Hmmm. Looks like something having to do with authenticating a user is commented out... welp, that was a mistake! Let's comment that back in and run our test again.

Hmmm... another failure...

Failures:

  1) ApplicationController GET #dashboard does not return a success response
     Failure/Error: get :dashboard

     Devise::MissingWarden:
       Devise could not find the `Warden::Proxy` instance on your request environment.
       Make sure that your application is loading Devise and Warden as expected and that the `Warden::Manager` middleware is present in your middleware stack.
       If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the `Devise::Test::ControllerHelpers` module to inject the `request.env['warden']` object for you.
     # ./spec/controllers/application_controller_spec.rb:14:in `block (3 levels) in <top (required)>'

Finished in 0.43267 seconds (files took 2.12 seconds to load)
19 examples, 1 failure

Let's take a closer look at that error message...

If you are seeing this on one of your tests, ensure that your tests are either executing the Rails middleware stack or that your tests are using the Devise::Test::ControllerHelpers module to inject the request.env['warden'] object for you.

Looks like we need to add some helpers!

Add Devise Test Helpers

At the top of spec/spec_helper.rb require devise, and add the below to the config block...

# in spec/spec_helper.rb

require 'devise' # at the top of your file

RSpec.configure do |config|
  # ... some other stuff ...

  config.include ControllerHelpers, type: :controller
  Warden.test_mode!

  config.after do
    Warden.test_reset!
  end

  # ...
end

Add the below to the config block in spec/rails_helper.rb

# in spec/rails_helper.rb

RSpec.configure do |config|
  # ... some other stuff ...

  config.include Devise::TestHelpers, type: :controller
  config.include Warden::Test::Helpers

  # ...
end

In spec/support create controller_helpers.rb...

touch spec/support/controller_helpers.rb

...and paste in the following contents...

# in spec/support/controller_helpers.rb

module ControllerHelpers
  def login_with(user = double('user'), scope = :user)
    current_user = "current_#{scope}".to_sym
    if user.nil?
      allow(request.env['warden']).to receive(:authenticate!).and_throw(:warden, {:scope => scope})
      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

Now require your new helper at the top of spec/spec_helper.rb

# in spec/spec_helper.rb

require_relative 'support/controller_helpers'

Now run tests to see what happens -- yippeee! They pass!

Practice (5 minutes)

$> git checkout controller_auth_testing_pre_exercises

Our controller test passes, but it's only currently testing behavior for anonymous users. Let's make our tests more robust.

  • Use context blocks to separate your cases and test set up for logged in vs non-logged in users.
  • Adding before { login_with } should simulate a user being logged in
  • Write tests for the following behavior...
    • when a user is logged in, get home redirects to the dashboard
    • when a user is not logged in, get home is successful
    • when a user is logged in, get dashboard is successful
    • when a user is not logged in, get dashboard redirects to the log in page

HINTS:

  • You can use expect(action).to redirect_to(some_application_path)
  • Run rake routes to see what routes are available
  • To get the rails url helper method, append _path to the route prefix from rake routes
  • devise/sessions#new is the login route

Solutions branch

$> git checkout controller_auth_testing_solutions

Further Reading

Hat Tips to...

... the following awesome resources that helped me set up routes and testing with devise

results matching ""

    No results matching ""