Railsアプリケーションを8.0にアップデートした際、RSpecのテストが個別実行では正常に動作するものの、テストスイート全体の実行ではスタブが効かなくなるという問題に遭遇しました。本記事では、遭遇した問題とその解決までの過程をまとめます。
遭遇したエラー
Rails 8.0へバージョンアップ後、RSpecを用いたテストで次のような現象に遭遇しました。
- テストを個別に実行した場合は正常にパスする
- テストスイート実行時には一部のスタブやモックが正しく機能せず、テストが失敗する
- タイミングによっては稀にテストがパスすることもある
特にテストスイートを実行した際に、スタブが効かなくなり、特定のテストで期待した挙動が得られないケースが発生しました。
具体的には、以下のような認証機構を持つコントローラに対して、logged_in?
メソッドのスタブが効かなくなるケースに遭遇しました。
# コントローラ例
# app/controllers/addresses_controller.rb
class AddressesController < ApplicationController
def index
# ログインしていない場合は401を返す
render status: :unauthorized unless logged_in?
address = Address.where(user_id: params[:user_id])
render json: { addresses: address }
end
private
# user_idが存在するか確認し、ログイン判定実施
def logged_in?
session[:user_id].present?
end
end
# テスト例
# spec/requests/address_spec.rb
require 'rails_helper'
RSpec.describe "Addresses API", type: :request do
describe "GET /addresses" do
let(:user) { create(:user) }
before do
create(:address, user: user)
allow_any_instance_of(AddressesController).to receive(:logged_in?).and_return(true)
end
context "ログインしている場合" do
it 'Addressの取得に成功すること' do
get '/addresses', params: { user_id: user.id }
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['addresses'].size).to eq(3)
end
end
end
end
このテストでは、logged_in? をスタブして強制的にログイン状態を作り、/addresses
エンドポイントのレスポンスを確認する流れになっています。
エラーの原因
今回のエラーの根本原因は、次の2点にありました。
- Springがアプリケーションプロセスをキャッシュしていること
- Rails 8.0のZeitwerkオートローダーがコード変更をリアルタイムに反映しない場合があること
これにより、テストスイート実行時に古いコードが使われ、スタブが正しく効かない現象が発生していました。
Springの役割
Springは、Railsアプリケーションの起動時間を短縮するために、プロセスを常駐させて高速化を図るツールです。
通常、Railsアプリは起動に時間がかかりますが、Springを利用することで、プロセスを再利用し、起動時間を大幅に短縮できます。
ただしこの仕組みは、
- アプリケーションコードや設定が変更されても即時反映されない
- 古い状態のプロセスが残り続ける
といったリスクを抱えています。
特に、テストスイートをまとめて実行する際には、キャッシュされた古いコードが原因でスタブやモックが効かない問題に繋がることがあります。
エラーの解決方法
この問題を解決するため、SpringのプロセスがforkされるタイミングでRailsアプリケーションを明示的にリロードする設定を追加しました。
# config/spring.rb
Spring.after_fork do
Rails.application.reloader.reload!
end
この設定により、Springがプロセスをforkするたびに、Railsのコードや定数が最新の状態にリロードされるようになり、テストスイート実行時でもスタブが正しく機能するようになりました。
まとめ
Rails 8.0へのアップデートに伴い、RSpecでスタブが効かなくなる問題に遭遇しましたが、SpringのキャッシュとZeitwerkオートローダーの動作に原因があることを突き止めました。
Springのafter_forkフックを利用して、アプリケーションコードを毎回リロードすることで、スタブやモックが正常に機能するようになり、問題を無事に解決できました。
同様の問題に遭遇した方は、ぜひこの方法を試してみてください!