loading...

Tips for testing: Authentication with devise_token_auth in Rails API

risafj profile image Risa Fujii ・3 min read

Previously, I wrote a blog post on how to create an authentication system for Rails API with the devise_token_auth gem.

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:

  1. How do you write tests for the authentication system?
  2. 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.

Posted on by:

risafj profile

Risa Fujii

@risafj

I'm a self-taught web developer working for a small tech company in Tokyo 👩🏻‍💻 I enjoy writing about new topics I learned in a beginner-friendly way.

Discussion

pic
Editor guide
 

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 what devise_token_auth provided. It turns out that when you use devise_token_auth with a resource you get one new method create_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:

def setup
  test_user_options = { email: 'user@test.com', password: 'testuser' }
  user = create_user(test_user_options) # factory to create user
  request.headers.merge! user.create_new_auth_token
end

I got the last line from the devise_token_auth testing documentation. With this version, you don't need the AuthorizationHelper.

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.