【Rails6】Omniauth × devise_auth_tokenを用いたTwitterログイン機能のRequest Specを書く


Rails × React SPAの作成に当たりOmniauth × devise_auth_tokenを用いたTwitterログイン機能を実装している。
その際のRequest Specの書き方についてメモ

どこをテストするか?

以下の記事内にいい感じの画像があったので引用。
https://qiita.com/hirakei1203/items/d7e03040a4f899d374c1

画像内の③から④のあたり(auth_hashを受け取ってその内容をもとにユーザーを保存する)を検証する

コード内容

実際に書いたコードがこちら

require 'rails_helper'

RSpec.describe 'OmniauthUsers', type: :request do
  before do
    OmniAuth.config.test_mode = true # これがないと実際にツイッターと通信してしまう
    OmniAuth.config.mock_auth[:twitter] = nil # テストごとに認証情報を初期化する
    Rails.application.env_config['omniauth.auth'] = twitter_mock
    Rails.application.env_config['omniauth.params'] = { 'resource_class' => 'User', 'namespace_name' => 'api_v1' } # No resource_class foundというエラーを避ける
  end

  describe 'omniauthを用いたTwitterでのログイン' do
    context 'ログインに成功' do
      it 'oauthのデータが存在する場合ログインに成功する' do
        get '/api/v1/users/twitter/callback'
        expect(response).to have_http_status(200)
      end

      it 'oauthのデータが存在する場合ユーザーモデルのカウントが1増える' do
        expect do
          get '/api/v1/users/twitter/callback'
        end.to change(User, :count).by(1)
      end

      it 'oauthのデータが存在する場合リクエストのmockのデータに応じたレスポンスが返却される' do
        get '/api/v1/users/twitter/callback'
        json = JSON.parse(response.body)
        expect(json['uid']).to eq request.env['omniauth.auth']['uid'] # 念の為一意性のカラムで検証
        expect(json['email']).to eq request.env['omniauth.auth']['info']['email'] # 念の為一意性のカラムで検証
        expect(json['provider']).to eq request.env['omniauth.auth']['provider'] # 念の為一意性のカラムで検証
      end
    end

    context 'ログイン失敗' do
      it "request.env['omniauth.auth']がnilの場合リクエストに失敗する" do
        Rails.application.env_config['omniauth.auth'] = nil
        expect do
          get '/api/v1/users/twitter/callback'
        end.to raise_error NoMethodError # undefined method [] for nil:NilClassと出る。auth_hashがnilのためにユーザー情報の取得に失敗している状態
      end
    end
  end
end

詳しく見ていこう

before do ~ end

  before do
    OmniAuth.config.test_mode = true # これがないと実際にツイッターと通信してしまう
    OmniAuth.config.mock_auth[:twitter] = nil # テストごとに認証情報を初期化する
    Rails.application.env_config['omniauth.auth'] = twitter_mock
    Rails.application.env_config['omniauth.params'] = { 'resource_class' => 'User', 'namespace_name' => 'api_v1' } # No resource_class foundというエラーを避ける
  end

コメントアウトで書いている通り。今回は3行目にあるtwitter_mockというのを用いてauth_hashを擬似的に生成する。
twitter_mockは以下のように作成

module OmniauthMocks
  def twitter_mock
    OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
      'provider' => 'twitter',
      'uid' => '123456',
      #  infoはTwitterのプロフィールと対応
      'info' => {
        'nickname' => 'mock_user',
        'image' => 'http://mock_image_url.com',
        'location' => '',
        'description' => 'This is a mock user.',
        'email' => '[email protected]',
        'urls' => {
          'Twitter' => 'https://twitter.com/MockUser1234',
          'Website' => ''
          }
        },
      'credentials' => {
        'token' => 'mock_credentails_token',
        'secret' => 'mock_credentails_secret'
        },
      'extra' => {
        'raw_info' => {
          'name' => 'Mock User',
          'id' => '123456',
          'followers_count' => 0,
          'friends_count' => 0,
          'statuses_count' => 0
          }
        }
      })
  end
end

作り方はいろいろあるだろうけど、自分の場合はsupportディレクトリ配下に配置しrails_helper.rbにて

Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
RSpec.configure do |config|
  config.include OmniauthMocks

とすることで読み込みを可能にしている。

またtwitter_mockを代入する以外にもtest_modeの有効化やmock_authをnilにする処理、Rails.application.env_config['omniauth.params'] の設定など
を行った。それについてはコメントアウトで示しているとおりです。

example

describe 'omniauthを用いたTwitterでのログイン' do
    context 'ログインに成功' do
      it 'oauthのデータが存在する場合ログインに成功する' do
        get '/api/v1/users/twitter/callback'
        expect(response).to have_http_status(200)
      end

      it 'oauthのデータが存在する場合ユーザーモデルのカウントが1増える' do
        expect do
          get '/api/v1/users/twitter/callback'
        end.to change(User, :count).by(1)
      end

      it 'oauthのデータが存在する場合リクエストのmockのデータに応じたレスポンスが返却される' do
        get '/api/v1/users/twitter/callback'
        json = JSON.parse(response.body)
        expect(json['uid']).to eq request.env['omniauth.auth']['uid'] # 念の為一意性のカラムで検証
        expect(json['email']).to eq request.env['omniauth.auth']['info']['email'] # 念の為一意性のカラムで検証
        expect(json['provider']).to eq request.env['omniauth.auth']['provider'] # 念の為一意性のカラムで検証
      end
    end

    context 'ログイン失敗' do
      it "request.env['omniauth.auth']がnilの場合ログインに失敗する" do
        Rails.application.env_config['omniauth.auth'] = nil
        expect do
          get '/api/v1/users/twitter/callback'
        end.to raise_error NoMethodError # undefined method [] for nil:NilClassと出る。auth_hashがnilのためにユーザー情報の取得に失敗している状態
      end
    end
  end
end


実際のexampleはこんな感じで基本は Rails.application.env_config['omniauth.auth'] がnilかどうかで正常形と異常形を分けている。
正常形はhttpステータス、Userのカウントの増加、レスポンスをjsonで受け取り中身がmockと一致することの3つを検証

感想

参考記事がなくて大変だったけど、どの処理をテストするのか?という点が明確であればそれなりにテスト書くこともできるな、という印象でした。いい経験でした。