1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Rails8のSystem Specでdevise gemのsign_inヘルパーメソッドで`Could not find a valid mapping for ...` というエラーが出た

1
Posted at

環境

  • Rails 8.0.x
  • Devise 4.9.x
  • RSpec + Capybara

ユーザーまたは管理者の認証機能にはdeviseを利用している環境です。

問題

System Specのbeforeブロックでsign_in userを呼ぶと、以下のエラーが発生する。

RuntimeError:  
  Could not find a valid mapping for #<User id: ...>

背景

specはこんな感じ

spec/system/users_spec.rb

require 'rails_helper'

describe 'ユーザーログイン', type: :system do
  let(:user) { create(:user) }

  before do
    sign_in user
  end

  it 'hogheohgoeghe' do
    visit hoge_path
    expect(page).to have_content 'hogehoge'
  end

試したこと

条件 結果
ファイル単体でspec実行 失敗
itブロック内でsign_inを呼ぶ 成功
sign_in user, scope: :userと明示的にスコープ指定 成功
他のspecファイル2つ以上と一緒に実行 成功

原因

Rails8のルート遅延読み込み(Lazy Route Loading)によって、devise_forによるmappingが効かず、sign_inメソッドでエラーが発生する

Rails8のルート遅延読み込み(Lazy Route Loading)とは

開発、テスト環境において、Rails起動時にルーティングを遅延読み込みさせる設定のことです

これにより、数千単位でルーティングが設定してあるRailsアプリケーションの起動を早めることに成功しています。

This Pull Request changes engine and app route sets to a Rails::Engine::RouteSet, which knows about the current Rails application. The default middleware stack has also changed to include a Rails::Rack::LoadRoutes middleware that loads routes if needed. This PR loads routes under the following circumstances:
In dev/test:

  • The first request via middleware
  • When application or engine url_helpers.some_path is called via method_missing?
  • When application or engine url_helpers.respond_to?(:some_path) is called via - respond_to_missing?

実際に、ルーティングが必要になるタイミングが来ないと読み込んでくれないみたいです

railsガイドには書いてないっぽいですね、記載があったらすみません

devise_forによるmappingとは

deviseを用いてルーティングを設定するときに、下記のように設定するとします。

routes.rb
  devise_for :users

このdevise_forが実行されると、devise内部でDevise.mappingsにマッピング情報が登録されます。

このマッピングには以下のような情報が含まれます

  • リソース名(:user
  • 使用するモデルクラス(User
  • 認証に関連するルート情報
  • 使用するコントローラー情報

sign_inメソッドは、このマッピング情報を参照してユーザーを認証します。

# Devise内部のイメージ
Devise.mappings[:user] #=> #

解決策

1.rails_helper.rbでルートを強制ロード

spec/rails_helper.rbに以下を追加:

Rails.application.reload_routes_unless_loaded

全てのsystem specで、beforeブロック実行前にルートが確実にロードされる

2.scopeを明示的に指定

spec/system/users_spec.rb

require 'rails_helper'

describe 'ユーザーログイン', type: :system do
  let(:user) { create(:user) }

  before do
    sign_in user, scope: :user # ここに追加
  end

  it 'hogheohgoeghe' do
    visit hoge_path
    expect(page).to have_content 'hogehoge'
  end

詳しくはdevise gemのdevise_forを読んで欲しいですが、明示的にスコープを指定してあげることもできます

3.eager_loadをtrueにする

config/environments/test.rb

config.eager_load = true  

起動時に全てがロードされるため問題は解決するが、テストの起動時間が長くなる

他にも方法はあるけど、ここまで

感想

  • Railsガイドのドキュメントではなく、Ruby on Railsのリリースノート直接みた方が早かったかも
  • PR最初から見ればよかった

参考

devise Devise::Mappingについて

Rails8遅延読み込みについてのPR

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?