【RSpec】Twitterログイン部分の結合テストコードを書いてみた【TwitterAPI】

#はじめに
[前の記事](https://qiita.com/guguasmon/items/c062146fd3301a373dd4)の続きです。

Twitter認証をDeviseなしで実装した後、
結合テストコードを記述した時のメモ書きです。
備忘録も兼ねて記事にしたいと思います。


【実行環境】
・ Rails 6.0.0
・ Ruby 2.6.5
・ RSpec 4.0.0

【使用gem】
gem 'omniauth'
gem 'omniauth-twitter'

【参考にさせていただいた記事】

https://qiita.com/ngron/items/88f04a2d864f9f33b389
https://www.wantedly.com/companies/clueit/post_articles/43708

詳細な説明はあまり載せていないので、詳しく知りたい場合は参考にさせていただいた記事を読んでいただければと思います。

#外部APIを使用したテストの場合
今回Twitterログインの部分をテストするにあたって問題となるのは外部APIであるTwitterAPIを使用していることにあります。
今回行いたいテストは「ログインしようとしているユーザーがTwitterのアカウント認証を許可したかどうか、によって変化したその後のログイン状況を確認する」ということです。
このテストを行うには、実際にTwitterAPIよりアカウント情報を取得しなくてはいけません。

しかしテストで外部APIを利用することには、幾つかのデメリットがあります。
テストにもかかわらず実際に投稿をしてしまったり、外部APIの使用に制限などがあったり、外部API周辺でのエラーがあったりです。

ということで「モック」を利用してダミーのユーザーデータを作成します。

https://qiita.com/jnchito/items/640f17e124ab263a54dd
モックについてはこちらが詳しいです。


#モック（テストで使うダミーデータ）の作成
ユーザーデータのハッシュが入ってるのが、request.env["omniauth.auth"]なので、
その部分にいれるモック（ダミーユーザーデータ）を擬似的に作ってテストをしていきます。

今回はサポートモジュールを用いて記述する形にします。
spec/support/omniauth_mocks.rbに書いて最後にincludeするようにします。
下記では認証に成功した時用のものと、認証に失敗した（コールバックに失敗した）時用の２種類を作成しています。

```ruby:spec/support/omniauth_mocks
module OmniauthMocks
  def twitter_mock
    OmniAuth.config.mock_auth[:twitter] = OmniAuth::AuthHash.new({
      "provider" => "twitter",
      "uid" => "123456",
      "info" => {
        "name" => "Mock User",
        "image" => "http://mock_image_url.com",
        "location" => "",
        "email" => "mock@example.com",
        "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

  def twitter_invalid_mock
    OmniAuth.config.mock_auth[:twitter] = :invalid_credentails
  end
end
```

#RSpecの設定

```ruby:spec/rails_helper.rb
~省略~
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } #サポートモジュールを使う場合はこの部分のコメントアウトを外しておく
~省略~

# OmniAuthをテストモードに変更
OmniAuth.config.test_mode = true

RSpec.configure do |config|
~省略~
  # サポートモジュールの読み込み（テストで使う仮のユーザーデータ）
  config.include OmniauthMocks
end
```

#ルーティング
ルーティングはこのように設定してあります

```ruby:config/routes.rb
get '/auth/:provider/callback', to: 'sessions#create' #ログイン認証
get '/logout', to: 'sessions#destroy' #ログアウト用
get "/auth/failure", to: "sessions#failure" #認証失敗時用
```

#テストコードを書くためのファイルを用意する

```ターミナル
% rails g rspec:system users
```

#実際に記述したテストコード

テストの前に`OmniAuth.config.mock_auth[:twitter]`を初期化し、`twitter_mock`あるいは`twitter_invalid_mock`をセットします。

```ruby:spec/system/users_spec.rb
require 'rails_helper'

RSpec.describe 'Users', type: :system do
  before do
    @user = FactoryBot.create(:user)
    OmniAuth.config.mock_auth[:twitter] = nil
  end

  context 'Twitter認証ができるとき' do
    it 'ログインボタンを押してユーザーがTwitter認証を許可した時' do
      Rails.application.env_config['omniauth.auth'] = twitter_mock
      visit root_path
      expect(page).to_not have_content('マイページ') # ログイン前はマイページという表示が無い
      find_link('ログイン', href: '/auth/twitter').click # ログインボタンをクリックしてTwitter認証を行う
      expect(page).to have_content('マイページ') # リダイレクトされてTOPに戻るとログインできている
    end
  end
  context 'Twitter認証ができないとき' do
    it 'ログインボタンを押してユーザーがTwitter認証をキャンセルした時' do
      Rails.application.env_config['omniauth.auth'] = twitter_invalid_mock
      visit root_path
      expect(page).to_not have_content('マイページ') # ログイン前はマイページという表示が無い
      find_link('ログイン', href: '/auth/twitter').click # ログインボタンをクリックしてTwitter認証を行う
      expect(page).to_not have_content('マイページ') # リダイレクトされてTOPに戻るとログインできてない
    end
  end
end
```

#FactoryBotの中身
これ自体にあまり意味はないので無理にfactorybotを使う必要はありません。

```ruby:spec/factories/users.rb
FactoryBot.define do
  factory :user do
    uid { 123456 }
    nickname { Faker::Name.first_name}
    name {Faker::Name.initials(number: 2)}
    image {Faker::Lorem.sentence}
  end
end

```

#テストコードの実行

```:ターミナル
% bundle exec rspec spec/system/users_spec.rb
```



#おわりに
メモ書き程度ですが、何かの一例になれば幸いです。
ついでに改善した方が良い部分などあれば指摘いただけると助かります。