[Rails] ActionView::TestCaseでsetup内で定義した変数が公開されない問題を修正
背景
Rails 8.0以降、ActionView::TestCaseを使用したビューのテストで、setupメソッド内で定義したインスタンス変数がビューに公開されない問題が発生していました。この問題は#56476のリークチェッカー強化により引き起こされていました。
従来、テストケース内で定義したインスタンス変数は自動的にビューコンテキストで利用可能でしたが、after_setupフックでインスタンス変数をキャプチャするタイミングの問題により、setupメソッドで定義した変数が正しく検出されなくなっていました。
問題の詳細
以前の実装では、after_setupメソッドで現在のインスタンス変数をキャプチャしていました:
def after_setup
@__setup_ivars = instance_variables << :@__setup_ivars
end
しかし、Minitestの実行フローではsetupメソッドがafter_setupの前に実行されるため、このアプローチではsetup内で定義された変数を正しくキャプチャできていませんでした。
修正内容
今回の修正では、after_setupフックを削除し、代わりにINTERNAL_IVARS定数にリークチェッカーの内部変数を追加することで問題を解決しています:
INTERNAL_IVARS = [
:@__memoized,
:@_assertion_wrapped,
:@_assertions,
:@_result,
:@_routes,
:@controller,
:@layouts,
:@locals,
:@method_name,
:@output_buffer,
:@partials,
:@passed,
:@rendered,
:@request,
:@routes,
:@tagged_logger,
:@templates,
:@updates,
:@view,
:@view_context,
:@view_context_class,
:@view_flow,
:@_subscribers,
:@html_document,
:@__leak_checker_before_env, # 新規追加
]
def _user_defined_ivars
instance_variables - INTERNAL_IVARS # @__setup_ivarsの参照を削除
end
技術的な意義
この修正により、以下のようなテストコードが正しく動作するようになります:
class MyViewTest < ActionView::TestCase
def setup
@user = User.new(name: "Alice")
end
test "renders user name" do
render inline: "<%= @user.name %>"
assert_select "body", "Alice"
end
end
_user_defined_ivarsメソッドは、テストケースで定義されたユーザー変数を抽出し、ビューコンテキストに転送する役割を担っています。静的な除外リストを使用することで、テストライフサイクルのどの時点で変数が定義されても、適切に検出・公開できるようになりました。