Previously, I wrote a blog post on how to create an authentication system for Rails API with the devise_token_auth
gem.
Guide to devise_token_auth: Simple Authentication in Rails API
Risa Fujii ・ Feb 9 '19
But to be honest, the most unintuitive part for me was not adding the authentication system itself, but testing it and any functions that use it. So this post focuses on testing, more specifically:
- How do you write tests for the authentication system?
- How do you write controller tests for functions that require authentication?
The first point is self-explanatory. As for the second: let's say you're creating an application for library books. You can add new books to the database with an HTTP POST request to /books
, but you need to be authenticated to do this. If no authentication was involved, you could test the method for adding books like below, but this test would fail in our case because the request is not authenticated.
# test/controllers/books_controller_test.rb
test 'should create new book' do
post 'books/',
params: {
name: 'Harry Potter'
}
assert_response :success
end
Note: Guide is for Linux or MacOS systems, and uses minitest for testing.
Overview
Here are the steps involved.
- Write helper authorization methods that we’ll use in our tests in
test/helpers/authorization_helper.rb
- Create
test/authorization_test.rb
for testing our authorization helper code - In the tests for methods that require authentication, call the authorization methods in
authorization_helper.rb
Step 1. Create authorization helper file
One solution to the problem I described above (tests failing for unauthenticated requests) is to add one step for login to the first part of each test for methods that require authentication. But this would result in a lot of redundant code. A better way to handle this is to make a helper file with the necessary authentication methods, and call those methods in your controller tests.
In test/helpers
, create a file called authorization_helper.rb
. Inside, write the methods you'll use in your tests, like signing in and getting the authentication tokens from the headers (demonstrated below).
module AuthorizationHelper
def sign_up(user)
# The argument 'user' should be a hash that includes the params 'email' and 'password'.
post '/auth/',
params: { email: user[:email],
password: user[:password],
password_confirmation: user[:password] },
as: :json
end
def auth_tokens_for_user(user)
# The argument 'user' should be a hash that includes the params 'email' and 'password'.
post '/auth/sign_in/',
params: { email: user[:email], password: user[:password] },
as: :json
# The three categories below are the ones you need as authentication headers.
response.headers.slice('client', 'access-token', 'uid')
end
end
Bear in mind that this code cannot be run as-is, since this is meant to be called inside tests.
Step 2. Test your authorization helper code
In your test
folder, create a file called authorization_test.rb
. Then, you can test the methods in your helper file.
For example:
test 'sign up and log in user one' do
user_one = { email: 'userone@test.com', password: 'password' }
sign_up(user_one)
assert_response :success
(user_one)
assert_response :success
end
Now that we've confirmed that authentication works, if your controller tests fail, you can assume that it's not because of authentication issues.
Step 3. Add your helper methods to tests that require authentication
Going back to our books app example - the controller test for adding new books needs to have authentication tokens in the HTTP request. So take the following steps.
1) Require and include your helper file
Include the helper file in the controller test where you're using authentication.
# Please use your actual relative path to this file.
require_relative '../helpers/authorization_helper'
Also, include this module like so.
class BooksControllerTest < ActionDispatch::IntegrationTest
include AuthorizationHelper
end
2) Authenticate in the setup method
The setup
method is called before every test, so put your authentication method here to avoid redundant code.
def setup
test_user = { email: 'user@test.com', password: 'testuser' }
sign_up(test_user)
@auth_tokens = auth_tokens_for_user(test_user)
end
3) Include the auth tokens in the headers
Then, simply include the auth tokens in the headers of your test. It's as simple as adding one line like below.
test 'should create new book' do
post 'books/',
params: {
name: 'Harry Potter'
},
headers: @auth_tokens
assert_response :success
end
And that's it! We've covered how to write tests for authorization code and how to send authenticated HTTP requests in other tests. Thank you for reading, and please let me know if anything needs clarifying.
Top comments (3)
I'm a little concerned for the time your tests will take to run. If you have to make two requests, one of which creates a user, before every authenticated test then the time is going to grow quickly as you add tests.
I recalled that
devise
on its own provides helper methods when you want to test, so I wondered whatdevise_token_auth
provided. It turns out that when you usedevise_token_auth
with a resource you get one new methodcreate_new_auth_token
which returns all the values you need.This means you can use existing users in your tests (if you are using fixtures) or you could use a factory to create a user. This would avoid the two calls to controllers before each test. Your
setup
function could look like this instead:I got the last line from the
devise_token_auth
testing documentation. With this version, you don't need theAuthorizationHelper
.What do you think? Could this simplify your tests and make them run quicker?
Thank you for your comment! Sorry for the late response - your point seems very valid and I will definitely look into it.
Thank you very much this is exactly what I need