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 therequest.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