[Rails]OmniauthのDeveloper StrategyとRspecを使ってユーザー認証をテストする
はじめに
Omniauthでgithubとgoogleログインを実装したのでこれらのテストについて見ていきます。
Rspecの部分はOmniauth公式のwikiを参考します。
OmniauthのDeveloper Strategyを使います。
One strategy, called Developer, is included with OmniAuth and provides a completely insecure, non-production-usable strategy that directly prompts a user for authentication information and then passes it straight through. You can use it as a placeholder when you start development and easily swap in other strategies later.
Developer StrategyはOmniAuthに付属し、ユーザに認証情報を直接入力させ、それをそのまま通過させる仕組みです。開発中にはこれをプレースホルダとして使い、後でGoogle
やGithub
等他のストラテジに入れ替えることができます。
ソーシャルログインを実装されていることを前提で進めていきます。
Devise + Omniauthのソーシャルログイン
環境
Rails 7.0.8
Ruby 3.2.1
tl;dr
- テスト用記述を追加する
- developerプロバイダーを追加する
- テストを作成する
テスト用記述を追加する
270行目あたりにdeveloper
ストラテジーを記述を追加します。
本番環境以外に使いたいのでif
文を追加します。
Devise.setup do |config|
# ==> OmniAuth
config.omniauth :developer unless Rails.env.production?
...
end
developerプロバイダーを追加する
google_auth
とgithub
と同じようにdeveloper
メソッドを追加します。
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_action :verify_authenticity_token, only: %i[google_oauth2 github developer]
...
def google_oauth2
handle_omniauth("Google")
end
def github
handle_omniauth("Github")
end
def developer
handle_omniauth("Developer")
end
private
def handle_omniauth(provider)
@user = User.from_omniauth(request.env["omniauth.auth"])
if @user.persisted?
sign_in_and_redirect @user, event: :authentication
set_flash_message(:notice, :success, kind: provider) if is_navigational_format?
else
session["devise.#{provider.downcase}_data"] = request.env["omniauth.auth"].except(:extra)
redirect_to root_path, alert: @user.errors.full_messages.join("\n")
end
end
end
developerプロバイダー用URLを追加する
class User < ApplicationRecord
devise :omniauthable, omniauth_providers: %i[google_oauth2 github developer]
end
developer用URLを追加されたことを確認します。
rails routes | grep omniauth
user_developer_omniauth_authorize GET|POST /users/auth/developer(.:format) users/omniauth_callbacks#passthru
user_developer_omniauth_callback GET|POST /users/auth/developer/callback(.:format) users/omniauth_callbacks#developer
http://localhost:3000/users/auth/developer
にアクセスし、Not found. Authentication passthru.
が表示されます。
セットアップが完了しました。テストを作成していきます。
ユーザーが登録できる
test/integration/
にomniauth_callback_test.rb
を作成します。
require "test_helper"
class OmniauthCallbacksTest < ActionDispatch::IntegrationTest
setup do
# テスト環境で OmniAuth ストラテジをテストモードに設定
OmniAuth.config.test_mode = true
# :developer ストラテジをモックしてテストデータを設定
OmniAuth.config.mock_auth[:developer] = OmniAuth::AuthHash.new({
provider: 'developer',
uid: '123',
info: {
email: 'test@example.com',
name: 'Test User',
image: nil
},
})
end
test "can signup w/ OAuth" do
assert_difference "User.count" do
get "/users/auth/developer/callback"
end
# ユーザーが指定通りに作成されたことを確認
user = User.last
assert_equal "123", user.uid
assert_equal "test@example.com", user.email
end
end
mock_auth
はOmniAuthテスト用に設定されたメソッドです。
テスト中に実際の外部認証プロバイダーへのリクエストを行わずに、テストデータを使ってプロバイダーへのリクエストをシミュレートするために使えます。
OmniAuth::AuthHash
に指定できるパラメータ:
info
の部分はユーザーモデルに合わせて設定しましょう。
assert_difference
はActiveSupport::TestCase
で提供されるテストヘルパーメソッドの1つです。
このメソッドは、特定の操作(通常はモデルのデータベースレコードの作成や削除)によってデータベース内のレコード数が期待通りに変更されたことを検証するのに使われます。
レコード数が少なくとも 1 以上増加していれば(これはデフォルトの動作です)、テストは合格します。レコード数が変化しないか減少した場合、テストは失敗します。
つまり、このテストは、/users/auth/developer/callback
へのアクセス時に、データベースに新しいユーザーレコードが作成されることを確認しています。期待どおりの動作であれば、テストは合格し、そうでなければ失敗します。
ここで一度テストを実行してみます。
rails test test/integration/omniauth_callbacks_test.rb
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 53607
# Running:
.
Finished in 0.483789s, 2.0670 runs/s, 6.2011 assertions/s.
1 runs, 3 assertions, 0 failures, 0 errors, 0 skips
OAuthのダミーデータの作成についてFakerを参考できます。
ユーザーがログインできる
test "can login w/ OAuth" do
# 1. テストユーザーの作成
user = User.create!(
email: "test@example.com",
name: "Test User",
password: "password",
password_confirmation: "password",
provider: :developer,
uid: "123"
)
user.skip_confirmation!
# 2. OAuthログイン
get "/users/auth/developer/callback"
# 3. ログイン状態の確認
get "/profile/edit"
assert_response :success
end
ユーザーをデータベースに作成し、OAuth認証のコールバックによってログインされます。
その後、プロフィール編集ページへのアクセスが成功するかどうかを確認しています。
プロフィール編集ページにアクセスするには認可が必要なためユーザーが正しく認証されていることを確認できます。
rails test test/integration/omniauth_callbacks_test.rb
Running 2 tests in a single process (parallelization threshold is 50)
Run options: --seed 63242
# Running:
..
Finished in 0.503905s, 3.9690 runs/s, 3.9690 assertions/s.
2 runs, 2 assertions, 0 failures, 0 errors, 0 skips
http://localhost:3000/users/auth/developer
にアクセスしログインすることもできます。
ログを見てみるとテストユーザーが作成されました。
18:49:28 web.1 | Started POST "/users/auth/developer/callback" for ::1 at 2023-10-07 18:49:28 +0900
18:49:28 web.1 | D, [2023-10-07T18:49:28.459829 #13295] DEBUG -- omniauth: (developer) Callback phase initiated.
18:49:28 web.1 | Processing by Users::OmniauthCallbacksController#developer as HTML
18:49:28 web.1 | Parameters: {"name"=>"Test User", "email"=>"test@example.com"}
...
irb(main):001:0> User.last
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 13, email: "test@example.com", created_at: "2023-10-07 18:49:28.748623000 +0900", updated_at: "2023-10-07 18:49:28.836602000 +0900", name: "Test User", avatar: nil, description: nil, provider: "developer", uid: "test@example.com", role: "general">
uid
はデフォルトでユーザーのメールアドレスです。
Ominiauthのパラメータをテストする
auth
オブジェクトを取得するためauth
メソッドを定義しましたが各々のパラメータだけを取得したい場合['omniauth.params']
を使うことができます。
def auth
request.env['omniauth.auth']
end
def omniauth_params
request.env['omniauth.params']
end
パラメータの値をユーザーに渡します:
User.create(
email: omniauth_params["email"], # request.env['omniauth.params']['email']
name: omniauth_params["email"],
password: "password",
password_confirmation: "password",
provider: omniauth_params["provider"],
uid: "omniauth_params["uid"]"
)
注意点としてはテストするときにPOST
リクエストを使います。
POST "/users/auth/developer/callback?name=Test+User&email=test@example.com"
Rspecでテストする
続いてRspecでログインの動きをテストします。gemをインストールします。
group :test do
gem 'rspec-rails'
gem 'factory_bot_rails'
gem 'capybara'
gem 'selenium-webdriver'
end
Chromeのバージョンを見つからないエラーが発生しました。
こちらの記事を参考して解決しました。
Omniauthモジュールを作成する
module OmniauthHelper
def mock_omniauth(provider, uid:, email:, name:)
OmniAuth.config.test_mode = true
OmniAuth.config.mock_auth[provider.to_sym] = OmniAuth::AuthHash.new({
provider: provider.to_s,
uid: uid,
info: {
email: email,
name: name
}
})
end
def mock_google_oauth2_auth
mock_omniauth(
'google_oauth2',
uid: '123456',
email: 'test@example.com',
name: 'Test User'
)
end
def mock_github_auth
mock_omniauth(
'github',
uid: 'github_uid',
email: 'github@example.com',
name: 'GitHub User'
)
end
end
rails_helper.rb
にある読み込み記述のコメントアウトを解除し、モジュールをincludeします。
# コメントアウトを解除する
Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f }
# モジュールをincludeする
RSpec.configure do |config|
config.include Devise::Test::ControllerHelpers, type: :controller
config.include Devise::Test::IntegrationHelpers, type: :request
config.include OmniauthHelper
end
システムspecを作成する
require 'rails_helper'
RSpec.describe "GitHub Authentication", type: :system, js: true do
include OmniauthHelper
before do
Rails.application.env_config["devise.mapping"] = Devise.mappings[:user]
Rails.application.env_config['omniauth.auth'] = OmniAuth.config.mock_auth[:github]
end
it "allows the user to sign in with GitHub" do
mock_github_auth
visit new_user_session_path
within('form.button_to[action="/users/auth/github"]') do
click_button 'Signin with Github'
end
expect(page).to have_content("Github アカウントによる認証に成功しました")
end
it "allows the user to sign in with Google" do
mock_google_oauth2_auth
visit new_user_session_path
within('form.button_to[action="/users/auth/google_oauth2"]') do
click_button 'Signin with Google'
end
expect(page).to have_content("Google アカウントによる認証に成功しました")
end
end
expect(page).to have_content
の部分をアプリに合わせて調整してください。
テストを実行してみます。
rspec spec/system/oauth_authentication_spec.rb
GitHub Authentication
allows the user to sign in with GitHub
allows the user to sign in with Google
Finished in 4.12 seconds (files took 2.45 seconds to load)
2 examples, 0 failures
Omniauthのテストケース
こちらはOmniAuthのモック機能のテストケースです。
認証プロバイダーと各パラメータが取得と設定されていることをテストしてます。
終わりに
Omniauthのテストを悩んだのでDeveloper Strategyを見つけて良かったです!
Discussion