devise_token_authのメール認証済みでないとログインできない機能をRSpecでテストする方法

はじめに
--
devise_token_authのログイン機能が開発し終わったので現在RSpecでテストを書いているのですが、"メール認証済みでないとログインできない"というテストを書くことができたので、備忘録として残そうと思います。

sessions_spec.rbで何をテストするか
--
devise_token_authで設定をすればアカウント作成時にメールで本認証を行わないとログインができない機能を実装することができます。
この"sessions_spec.rb"は、ログイン機能を担当しているので"メールが送信できているか？"などのテストではなくシンプルに、
~~~
・認証が済んでいればそのアカウントはログインできる
・認証ができていなければそのアカウントはログインできない
~~~
という2点が問題なく動作していることをテストで確認できればOKという考えになりました。
アカウントを作成する箇所のテストについては、registrations_spec.rbで記述します。
（その他パスワードやメールアドレスが間違っている場合はログインできない等のテストは書いていますが、認証とは関係ないので省略しています）

どうやって認証の状態を制御するか
--
結論から述べると"confirmed_at"に日付を入れることにより、認証済みか未認証かを制御することができます。
メール認証が完了(認証メール内にあるリンクを開くこと）でconfirmed_atに値が入り、認証済みかどうか判断しているようです。

最初に作成されるDBテーブルのカラム内にもちゃんとありますね。

~~~ruby
class DeviseTokenAuthCreateUsers < ActiveRecord::Migration[6.1]
  def change
    
    create_table(:users) do |t|
      ## Required
      t.string :provider, :null => false, :default => "email"
      t.string :uid, :null => false, :default => ""

      #省略
  
      ## Confirmable
      t.string   :confirmation_token
      t.datetime :confirmed_at #=>これ！！
      t.datetime :confirmation_sent_at
      t.string   :unconfirmed_email # Only if using reconfirmable

      #省略

  end
end
~~~

https://github.com/heartcombo/devise/blob/c82e4cf47b02002b2fd7ca31d441cf1043fc634c/lib/devise/models/confirmable.rb#L79

結論
--
そのため下記のように、confirmed_atに値がある場合/ない場合のuserデータを作成してそれぞれテストを行えばOKです。

~~~Ruby
RSpec.describe "Api::V1::Auth::Sessions", type: :request do
  let(:current_user) { create(:user, confirmed_at: Date.today) }

  describe "POST /api/v1/auth/sign_in" do
    context "認証時みでメールアドレス、パスワードが正しい場合" do
      let(:params) { { email: current_user.email, password: current_user.password } }

      it "ログインが成功すること" do
        post api_v1_user_session_path, params: params
        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(:no_confirmed_current_user) { create(:user, confirmed_at: "") }
      let(:params) { { email: no_confirmed_current_user.email, password: no_confirmed_current_user.password } }

      it "ログインが失敗すること" do
        post api_v1_user_session_path, params: params
        res = JSON.parse(response.body)
        expect(res["errors"]).to include("A confirmation email was sent to your account at '#{no_confirmed_current_user.email}'. You must follow the instructions in the email before your account can be activated")
        expect(response).to have_http_status(401)
      end
    end
  end
end
~~~

テストも無事通りました。
~~~
コンテナ名:/サービス名 # bundle exec rspec ./spec/requests/api/v1/auth/sessions_spec.rb
.....

Finished in 0.57591 seconds (files took 3.55 seconds to load)
5 examples, 0 failures
~~~

【余談】エラーメッセージはどこにあるのか
--
いろんなrspecを参考にしながらも、response.bodyのエラーメッセージとかどこのこと言ってんねん！と密かに思っていた時期があったので、一応どこにあるか画像に残そうと思います。
（何を写しちゃだめかわからなくて不安なので、一応不要そうな箇所は隠しました。)
![スクリーンショット 2022-09-03 0.49.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641341/1dcaf712-2da1-e860-2013-638b21a17adf.png)

終わりに
--
テストを書くために必要な準備は、どんなロジックで実装しているのか、もしくはされているのかの理解だと強く感じました。
gemを使って機能を作ってる時は理解してないとなんとなくで機能が作れちゃったりすると思うので、ちゃんとソースコードを読むことが大事だなぁとも感じました。