【RSpec】Devise を利用した Google 認証のテスト設定および実行手順

# はじめに
* RSpec で devise + omniauth-google-oauth2 の構成で認証を利用している場合のリクエストテスト、システムテストを実施したい方を対象としております
    * よって devise で Google 認証の実装が完了している前提です

## 環境など
| 開発環境一覧 |
|:-:|
| Windows11 Home 24H2 |
| Ubuntsu |   |
| Docker |
| VScode |
| Rails 7.2.1 |

### 参考コード
<details><summary>compose.yml</summary>

```compose.yml
services:
  db:
    image: postgres
    restart: always
    environment:
      TZ: Asia/Tokyo
      POSTGRES_PASSWORD: password
    volumes:
      - postgresql_data:/var/lib/postgresql
    ports:
      - 5432:5432
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d myapp_development -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    command: bash -c "bundle install && bundle exec rails db:prepare && rm -f tmp/pids/server.pid && ./bin/dev"
    tty: true
    stdin_open: true
    volumes:
      - .:/myapp
      - bundle_data:/usr/local/bundle:cached
      - node_modules:/myapp/node_modules
    environment:
      TZ: Asia/Tokyo
      SELENIUM_DRIVER_URL: http://selenium_chrome:4444/wd/hub
    ports:
      - "3000:3000"
    user: "1000:1000"
    depends_on:
      db:
        condition: service_healthy
  selenium_chrome:
    image: selenium/standalone-chrome
    ports:
      - "4444:4444"
    logging:
      driver: none
volumes:
  bundle_data:
  postgresql_data:
  node_modules:
```

</details>

<details><summary>Gemfile</summary>

```ruby:Gemfile
gem "rails", "~> 7.2.1"
gem "devise", "~> 4.9"

group :test do
  # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing]
  gem "capybara"
  gem "selenium-webdriver"
  gem "webdrivers"
end
```

</details>

# 1. ユーザーの情報を管理するモデルクラスの確認

:::note info
* 参考資料
    * https://github.com/heartcombo/devise/wiki/OmniAuth%3A-Overview
:::

* `def self.from_omniauth(auth)` といったメソッドを定義して **Google 認証時に"どんな情報を取得してきているか"など** を定義しているかと思います
    * 人によっては Google アカウント名、プロフィール画像を合わせて取得してきていると思われるので事前に確認
　
* 以下、私の例になりますが Google 認証時に 'Google メールアドレス' を取得するように定義しております
    * またメールアドレス認証を導入しているため、Google 認証利用時はメール認証をスキップするようにしております

```ruby:app/models/user.rb
class User < ApplicationRecord
########## 【前略】##########

  # https://github.com/heartcombo/devise/wiki/OmniAuth%3A-Overview
  def self.from_omniauth(auth)
    user = where(provider: auth.provider, uid: auth.uid).first_or_initialize do |user|
      user.email = auth.info.email
      user.password = Devise.friendly_token[0, 20]
    end
    user.skip_confirmation! # メール認証のスキップ
    user.save
    user
  end

########## 【後略】 ###########
end
```

# 2. RSpec テストで Devise 関連の設定を施す

:::note info
* 参考資料
    * https://github.com/omniauth/omniauth/wiki/Integration-Testing
:::

```ruby:spec/rails_helper.rb
RSpec.configure do |config|

########## 【中略】 ###########

  # リクエストおよびシステムのテストを実施したため以下のような記述
  config.include Devise::Test::IntegrationHelpers, type: :system
  config.include Devise::Test::IntegrationHelpers, type: :request
  config.include Warden::Test::Helpers
end

OmniAuth.configure do |c|
  c.test_mode = true
  c.mock_auth[:google_oauth2] = OmniAuth::AuthHash.new({
    provider: "google_oauth2",
    uid: "12345abcde",
    info: {
      email: "john@example.com",
    }
  })
end
```

:::note warn
```ruby:
OmniAuth::AuthHash.new({
    provider: "google_oauth2",
    uid: "12345abcde",
    info: {
      email: "john@example.com",
    }
  })
```
* `info` には `email` しか指定していませんが、これは前の手順の **Google 認証時に"どんな情報を取得してきているか"** で確認できた同じ項目を指定してください
:::

:::note info
* `モック` (Mock) … 「見せかけの、模型」
    * 疑似的に"Google 認証が成功した"と想定するテスト用の仮のオブジェクト
:::

# 3. テスト内容の記述および実行

:::note info
* 参考資料
    * https://github.com/omniauth/omniauth/wiki/Integration-Testing#setting-up-the-controller
:::

```ruby:spec/factories/users.rb
FactoryBot.define do
##########【中略】##########  

  factory :google_user, class: User do
    sequence(:email) { |n| "TEST#{n}@example.com" }
    password { "testuser123" }
  end
end

```

:::note warn
* 公式ドキュメントにもある通り以下の記述が必要となる
    * Twitter ではないので `google_oauth2` に変更
```ruby:
before do
  Rails.application.env_config["devise.mapping"] = Devise.mappings[:user] # If using Devise
  Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
end
```
:::

```ruby:spec/requests/signup_spec.rb
RSpec.describe "/XXXXX", type: :request do
  describe 'GET /XXXXX' do
    let(:google_user) { create(:google_user) }

    before do
      # https://github.com/omniauth/omniauth/wiki/Integration-Testing
      Rails.application.env_config["devise.mapping"] = Devise.mappings[:user]
      Rails.application.env_config["omniauth.auth"] = OmniAuth.config.mock_auth[:google_oauth2]
      # メール認証のスキップ
      google_user.confirm
      # 認証のパス
      get '/users/auth/google_oauth2/callback', params: { provider: "google_oauth2" }
    end
    it 'レスポンスでHTTPメッセージ302を返すこと' do
      expect(response).to have_http_status(302)
    end
  end
end
```

```ruby:spec/system/user_spec.rb
##########【前略】##########  

    it "Google認証が成功する" do
      click_button('Continue with Google')
      expect(page).to have_content('google アカウントによる認証に成功しました。')
    end
  end

##########【後略】##########  
```

### 参考資料

https://github.com/heartcombo/devise/wiki/OmniAuth:-Overview

https://github.com/omniauth/omniauth/wiki/Integration-Testing

https://sakitadaiki.hatenablog.com/entry/2021/04/02/090613

https://qiita.com/niwatoliver/items/e54ab46b382a19dcbbf4

https://qiita.com/kat0/items/2353b2e2952c2c8e9186

https://qiita.com/k_kuma/items/8857c9fcaf1bd8d73f59

https://zenn.dev/nope/articles/3abea632624c6f

https://github.com/zquestz/omniauth-google-oauth2