【Rails API】devise_token_auth実装〜Rspecテストまで【Rspecテスト編】

- [deviceの実装](https://qiita.com/disk131/items/10f33c666fcc952fcef7)

- [外部ツールで確認](https://qiita.com/disk131/items/528bfde1456289ed9963)
の記事を書きました。

いよいよ大事なテストを書いていきます。

この記事では、テスト項目について書きます。

Rspecの基礎的な書き方については対象外とします。

[こちらの記事](https://qiita.com/disk131/items/10f33c666fcc952fcef7)でdeviceを実装した前提です。

**記事の完成データはこちら**
https://github.com/daisuke131/devise_token_auth_rspec

## 必要なファイルを作成

```sh
$ bundle exec rails g rspec:model user
```

これにより、`spec/models/user_spec.rb`と`spec/factories/users.rb`が生成されます。

**続いて`spec/requests/api/v1/auth/registrations_spec.rb`を作成します**

これでファイルが揃いました。

## factoriesファイル修正

```spec/factories/users.rb
FactoryBot.define do
  factory :user do
    name { Faker::Name.name }
    sequence(:email) { |n| "#{n}_" + Faker::Internet.email }
    password { Faker::Internet.password(8) }
  end
end
```
emailは一意テストをするので重複しないよう念の為、emailの最初に番号をつけてます。

## モデルのテスト

### テスト項目

ここではUserモデルの保存処理のテストを行います。

- 全カラムの値を指定してるとき、レコードが作成されること
- emailを指定していないとき、エラーになること
- passwordを指定していないとき、エラーになること
- すでに保存されているemailを指定したとき、エラーになること

```spec/models/user_spec.rb
require 'rails_helper'

RSpec.describe User, type: :model do
  describe "validates presence" do
    context "全カラムの値を指定しているとき" do
      let(:user) { create(:user) }

      it "userのレコードが作成される" do
        expect(user).to be_valid
      end
    end

    context "emailを指定していないとき" do
      let(:user) { build(:user, email: nil) }

      it "エラーになる" do
        user.valid?
        expect(user.errors.messages[:email]).to include "can't be blank"
      end
    end

    context "passwordを指定していないとき" do
      let(:user) { build(:user, password: nil) }

      it "エラーになる" do
        user.valid?
        expect(user.errors.messages[:password]).to include "can't be blank"
      end
    end
  end

  describe "validates uniqueness" do
    context "保存されたメールアドレスが指定されたとき" do
      let(:user1) { create(:user) }
      let(:user2) { build(:user, email: user1.email) }

      it "エラーになる" do
        user2.valid?
        expect(user2.errors.messages[:email]).to include "has already been taken"
      end
    end
  end
end
```

devise_token_authでは、デフォルトで`validates :email, :password, presence: true`と`validates :email, uniqueness: true`が掛かっているのでこのようなテスト項目になりました。
仕様に応じてnameをpresence: trueにしてテストしてみたりしてください。
同じように書けばテスト通ります。

## コントローラのテスト

### テスト項目

ここではコントローラの処理のテストを行います。
routes.rbで指定されているルートに沿ってテストします。
今回コントローラー側でpassword_confirmをユーザー登録の条件に含めているのでそれもテストしていきます。

- ユーザー情報保存処理
  - ユーザーが登録できること
     - response.body["status"] = "success"
     - response.body["data"]["id"] = 登録したユーザーid
     - response.body["data"]["email"] = 登録したユーザーemail
     - response.status = 200
  - password_confirmationの値がpasswordと異なるときユーザー登録できない。
     - password_confirmationを空白で登録しようとする
     - response.body["status"] = "error"
     - エラーメッセージに"Password confirmation doesn't match Password"が含まれる。
     - response.status = 422
- ログイン処理
  - email、passwordが正しいとき、ログインできること
     - response.headers["uid"]が存在する
     - response.headers["access-token"]が存在する
     - response.headers["client"]が存在する
     - response.status = 200
  - emailが正しくない場合、ログインできないこと
     - response.body["success"] = false
     - response.body["errors"]が想定したエラー文と等しい
     - response.headers["uid"]が空
     - response.headers["access-token"]が空
     - response.headers["client"]が空
     - response.status = 401
  - passwordが正しくない場合、ログインできないこと
     - response.body["success"] = false
     - response.body["errors"]が想定したエラー文と等しい
     - response.headers["uid"]が空
     - response.headers["access-token"]が空
     - response.headers["client"]が空
     - response.status = 401
- ログアウト処理
  - ログインしているとき、ログアウトできること
     - response.body["success"] = true
     - カレントユーザーのreload.tokensが空であること
     - response.status = 200

```spec/requests/api/v1/auth/registrations_spec.rb
require "rails_helper"

RSpec.describe "Api::V1::Auth::Registrations", type: :request do
  describe "POST /api/v1/auth" do
    subject { post(api_v1_user_registration_path, params: params) }
    context "値が正しく入力されているとき" do
      let(:params) { attributes_for(:user) }
      it "ユーザー登録できる" do
        subject
        res = JSON.parse(response.body)
        expect(res["status"]).to eq("success")
        expect(res["data"]["id"]).to eq(User.last.id)
        expect(res["data"]["email"]).to eq(User.last.email)
        expect(response).to have_http_status(200)
      end
    end

    context "password_confirmationの値がpasswordと異なるとき" do
      let(:params) { attributes_for(:user, password_confirmation: "") }
      it "ユーザー登録できない" do
        subject
        res = JSON.parse(response.body)
        expect(res["status"]).to eq("error")
        expect(res["errors"]["full_messages"]).to include("Password confirmation doesn't match Password")
        expect(response).to have_http_status(422)
      end
    end
  end

  describe "POST /api/v1/auth/sign_in" do
    subject { post(api_v1_user_session_path, params: params) }
    context "メールアドレス、パスワードが正しいとき" do
      let(:current_user) { create(:user) }
      let(:params) { { email: current_user.email, password: current_user.password } }
      it "ログインできる" do
        subject
        expect(response.headers["uid"]).to be_present
        expect(response.headers["access-token"]).to be_present
        expect(response.headers["client"]).to be_present
        expect(response).to have_http_status(200)
      end
    end

    context "メールアドレスが正しくないとき" do
      let(:current_user) { create(:user) }
      let(:params) { { email: "test@example.com", password: current_user.password } }
      it "ログインできない" do
        subject
        res = JSON.parse(response.body)
        expect(res["success"]).to be_falsey
        expect(res["errors"]).to include("Invalid login credentials. Please try again.")
        expect(response.headers["uid"]).to be_blank
        expect(response.headers["access-token"]).to be_blank
        expect(response.headers["client"]).to be_blank
        expect(response).to have_http_status(401)
      end
    end

    context "パスワードが正しくないとき" do
      let(:current_user) { create(:user) }
      let(:params) { { email: current_user.email, password: "password" } }
      it "ログインできない" do
        subject
        res = JSON.parse(response.body)
        expect(res["success"]).to be_falsey
        expect(res["errors"]).to include("Invalid login credentials. Please try again.")
        expect(response.headers["uid"]).to be_blank
        expect(response.headers["access-token"]).to be_blank
        expect(response.headers["client"]).to be_blank
        expect(response).to have_http_status(401)
      end
    end
  end

  describe "DELETE /api/v1/auth/sign_out" do
    subject { delete(destroy_api_v1_user_session_path, headers: headers) }
    context "ユーザーがログインしているとき" do
      let(:current_user) { create(:user) }
      let(:headers) { current_user.create_new_auth_token }
      fit "ログアウトできる" do
        subject
        res = JSON.parse(response.body)
        expect(res["success"]).to be_truthy
        expect(current_user.reload.tokens).to be_blank
        expect(response).to have_http_status(200)
      end
    end
  end
end
```

こんな感じで書いてみました。

## おわり

devise_token_auth実装〜Rspecテストを３記事に分けて書きました。

[devise実装編](https://qiita.com/disk131/items/10f33c666fcc952fcef7)

[外部ツールで確認編](https://qiita.com/disk131/items/528bfde1456289ed9963)

テスト項目がまだ未熟かもしれません。

気になった方は教えてください！