【Rspec・devise token auth】Rails APIアプリのRspecテストでユーザログイン・認証を実装する方法


Rspecテスト内でdevise token authをつかってユーザログイン・認証するのはどうするんだ!?
Deviseのやり方はたくさんあるのですがdevise token authは探すのに苦労したので、私と同じ状況に陥っている方のためにこの記事を残しておきます。

やりたいこと

下記の様に、BooksControllercreateメソッドがあり、createメソッドを実行するにはユーザが認証が必須(authenticate_user!)だとします。

books_controller.rb
class BooksController < ApplicationController
  before_action :authenticate_user!

  def create
    book = Book.new(book_params)

    if book.save
     render json: { status: 'SUCCESS', data: book }
    else
     render json: { status: 'ERROR', data: book.errors }
    end
  end

  def book_params
   params.require(:book).permit(:name, :category, :author, :price)
  end
end

Rspecテストでユーザ認証を実装せずに書くと下記の様な感じになりますが・・・

books_spec.rb
require 'rails_helper'

RSpec.describe 'BooksAPI', type: :request do
  describe 'POST /books/create' do
    it '本を新規登録する' do
      params = {
        book: {
          name: "Rspecがよくわかる本",
          author: "豊田桃子",
          category: "プログラミング",
          price: 1400
        }
      }

      post books_path, params: params
      expect(response).to have_http_status :ok
    end
  end
end

もちろんauthenticate_user!が通らないので、expected the response to have status code :ok (200) but it was :unauthorized (401)というエラーが返ってきます。

BooksAPI
  POST books/create
    本を新規登録する (FAILED - 1)

Failures:

  1) BooksAPI POST books/create 本を新規登録する
     Failure/Error: expect(response).to have_http_status :ok
       expected the response to have status code :ok (200) but it was :unauthorized (401)
     # ./spec/requests/books_spec.rb:17:in `block (3 levels) in <top (required)>'

Finished in 0.14515 seconds (files took 3.38 seconds to load)
1 example, 1 failure

ということでdevise token authを使ってtokenベースで認証を行っている場合のRspecでのテストの書き方を解説したいと思います。

手順

  1. ヘルパーモジュールを作りsign_inメソッドを実装する
  2. ヘルパーモジュールをRspecで読み込む
  3. テストでヘルパーモジュールを使ってsign_inする

ヘルパーモジュールを作りsign_inメソッドを実装する

libディレクトリ直下にauthorization_spec_helper.rbという名前のファイルを作成します。下記のコードを貼り付けます。

lib/authorization_spec_helper.rb
module AuthorizationSpecHelper
  def sign_in(user)
    post "/auth/sign_in/",
      params: { email: user[:email], password: user[:password] },
      as: :json

    response.headers.slice('client', 'access-token', 'uid')
  end
end

userを使ってdevise_token_authのPOSTメソッドにてサインイン後、responseとして返ってきたclientaccess-tokenuidを返すヘルパーメソッドです。

ヘルパーモジュールをRspecで読み込む

ヘルパーモジュールをRspecで使える様にするために下記の一文を追加します。

rails_helper.rb
RSpec.configure do |config|
  # ....
  # ....
  config.include AuthorizationSpecHelper, type: :request
end

テストでヘルパーモジュールを使ってsign_inする

これでヘルパーモジュールを使う準備ができました。あとはbooks_spec.rbを修正していくだけです。

books_spec.rb
require 'rails_helper'

RSpec.describe 'BooksAPI', type: :request do
  describe 'POST books/create' do
    it '本を新規登録する' do
      # 下記の2行を追加する
      # DBに下記user情報のuserが存在している前提
      user = { email: "[email protected]", password: "123456" }
      auth_tokens = sign_in(user)

      params = {
        book: {
          name: "Rspecがよくわかる本",
          author: "豊田桃子",
          category: "プログラミング",
          price: 1400  
        }
      }

      # headersを追加
      post books_path, params: params, headers: auth_tokens

      expect(response).to have_http_status :ok
    end
  end
end

これで再度テストを実行してみると・・・・通った!!!

BooksAPI
  POST books/create
    本を新規登録する

Finished in 0.7509 seconds (files took 4.08 seconds to load)
1 example, 0 failures

これでもOK

require 'rails_helper'

RSpec.describe 'BooksAPI', type: :request do
  describe 'POST books/create' do
    let(:user) { { email: "[email protected]", password: "123456" } }
    let(:auth_tokens) { sign_in(user) }

    it '本を新規登録する' do
      params = {
        book: {
          name: "Rspecがよくわかる本",
          author: "豊田桃子",
          category: "プログラミング",
          price: 1400  
        }
      }

      post books_path, params: params, headers: auth_tokens

      expect(response).to have_http_status :ok
    end
  end
end

調べるまでが大変でしたが、実装自体はとても簡単でした。お役に立てれば嬉しいです!