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



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

## やりたいこと

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

```ruby: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テストでユーザ認証を実装せずに書くと下記の様な感じになりますが・・・

```ruby: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)`というエラーが返ってきます。

```terminal
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`という名前のファイルを作成します。下記のコードを貼り付けます。

```ruby: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として返ってきた`client`、`access-token`、`uid`を返すヘルパーメソッドです。

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

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

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

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

```ruby:books_spec.rb
require 'rails_helper'

RSpec.describe 'BooksAPI', type: :request do
  describe 'POST books/create' do
    it '本を新規登録する' do
      # 下記の２行を追加する
      # DBに下記user情報のuserが存在している前提
      user = { email: "momoko@test.com", 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
```

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

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

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

これでもOK

```ruby
require 'rails_helper'

RSpec.describe 'BooksAPI', type: :request do
  describe 'POST books/create' do
    let(:user) { { email: "momoko@test.com", 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
```

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