ネストしたファクトリのデバッグを支援する `factory_bot.before_run_factory` イベント
factory_botに factory_bot.before_run_factory という新しい ActiveSupport::Notifications イベントが追加されました。これにより、深くネストしたファクトリの呼び出しスタックをランタイムで構築できるようになり、after コールバックで発生するエラーの追跡が大幅に容易になります。
背景
ネストした関連を持つファクトリでエラーが発生した場合、エラーメッセージにはトップレベルのファクトリ名しか現れず、どのファクトリチェーンがエラーに至ったかを追跡することが困難でした。これが #1673 で報告された問題の本質です。
この問題を解決するための既存手段はいずれも限界を抱えていました。グローバルコールバックはコールバック内からファクトリ名を取得できず、factory_bot.compile_factory イベントはファクトリ定義のキャッシュにより複数のスペック実行時には再発火しません。run_factory イベントの start/finish サブスクライバを使う方法は技術的には機能しますが、ほとんどのユーザーが気づかない難解なパターンでした。
これらの代替手段がいずれも不十分であったため、ファクトリ実行直前に発火する専用のインストゥルメンテーションイベントが必要とされていました。
技術的な変更
lib/factory_bot/factory_runner.rb に1行を追加することで、既存の run_factory イベントの直前に新しいイベントが発火するようになりました。
変更前:
ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do
factory.run(runner_strategy, @overrides, &block)
end
変更後:
ActiveSupport::Notifications.instrument("factory_bot.before_run_factory", instrumentation_payload)
ActiveSupport::Notifications.instrument("factory_bot.run_factory", instrumentation_payload) do
factory.run(runner_strategy, @overrides, &block)
end
新しい instrument 呼び出しはブロックを持たない非ブロック形式であることに注目してください。run_factory はブロックを渡してファクトリの実行全体を計測するのに対し、before_run_factory はファクトリ実行の開始を通知するだけなので、ブロックを持たない単発の発火として実装されています。
ペイロードは既存の instrumentation_payload を再利用しており、:name、:strategy、:traits、:overrides、:factory という run_factory と同一のキーが提供されます。新たなペイロード構築のコードは一切不要です。
設計判断
before_run_factory と run_factory をプッシュ/ポップのペアとして使う設計が採用されました。
ドキュメントとテストで示されているユースケースは、配列によるコールスタックの構築です。before_run_factory でファクトリ名をスタックに積み、run_factory の完了時にポップすることで、任意のタイミングにおけるファクトリのネスト状態を把握できます。
factory_call_stack = []
ActiveSupport::Notifications.subscribe("factory_bot.before_run_factory") do |name, start, finish, id, payload|
factory_call_stack.push(payload[:name])
end
ActiveSupport::Notifications.subscribe("factory_bot.run_factory") do |name, start, finish, id, payload|
factory_call_stack.pop
end
run_factory のブロック計測モデル(開始時に start、終了時に finish が発火する)と組み合わせることで、before_run_factory の start イベントと run_factory の finish イベントを対応させるより、このプッシュ/ポップパターンのほうがサブスクライバの実装をシンプルに保てます。また、ペイロードを新規に設計せず既存の instrumentation_payload を流用することで、変更範囲が最小限に抑えられています。
まとめ
本PRは factory_runner.rb への1行追加という最小限の変更で、深くネストしたファクトリのデバッグという長年の課題に対処しています。既存のインストゥルメンテーション基盤と同一のペイロードを再利用する設計により、ユーザーは新しいAPIを学ぶことなく、使い慣れた ActiveSupport::Notifications のパターンでファクトリ呼び出しスタックを構築できます。