ActionView::TestCase#render が rendered をリセットするよう修正
ActionView のテストケースにおいて、render 呼び出し後に rendered が前回の出力を保持し続ける不整合を解消し、ドキュメントが示す 「最後の render 呼び出しの出力」 という振る舞いに合わせました。
背景
ActionView::TestCase#rendered の挙動が期待と食い違っていたのは、#51093 で導入されたメモ化機構が原因です。この変更により @rendered は単なる String から RenderedViewContent サブクラスに置き換えられ、render が毎回 @rendered.clear を呼び出す従来のリセットロジックが機能しなくなりました。#56235 で指摘されたように、ドキュメントは 「最後の render の出力」 と明記しているものの、実際のテストは複数回の render が出力を累積することを前提としていました。結果として、テストコードと実装が相反し、テストの信頼性が低下していました。そこで本 PR は、rendered が各 render 呼び出しごとに正しくリセットされるよう実装を修正しました。
技術的な変更
本修正は ActionView::TestCase#render の実装に対し、@rendered の再初期化とレンダリング状態の管理ロジックを追加した点が中心です。まず setup_with_controller で @rendered の直接初期化を削除し、代わりにプライベートメソッド _reset_rendered を呼び出すように変更しました。
@@
- @rendered = self.class.content_class.new(+"")
+ _reset_rendered
_reset_rendered は以下のように実装され、content_class が提供する任意のクラスでも新しいインスタンスを生成できるようにしています。
def _reset_rendered
@rendered = self.class.content_class.new(+"")
end
render メソッド自体も大幅に変更され、最初の呼び出し時に _reset_rendered を実行し、以降のネストした呼び出しでは既存の出力バッファを再利用するためのフラグ @_rendering を導入しました。具体的には、@_rendering が未設定の場合にだけリセットを行い、ensure 節でフラグを確実に解除しています。
@@
- @rendered << output = view.render(options, local_assigns, &block)
- output
+ if @_rendering
+ view.render(options, local_assigns, &block)
+ else
+ _reset_rendered
+ @_rendering = true
+ output = view.render(options, local_assigns, &block)
+ @rendered << output
+ output
+ end
+ ensure
+ @_rendering = false unless @_rendering.nil?
さらに、インスタンス変数 @_rendering を view の状態リストに追加し、inspect での可視性を確保しました。これにより、レンダリング中に二重呼び出しが発生した場合でも rendered が不必要にリセットされることはなくなります。テスト側も新たに 「nested helper renders do not replace rendered」 というケースが追加され、複数回の render 後に前回の出力が残っていないことが検証されています。全体として、rendered のリセットロジックが復元され、ドキュメント通りの振る舞いが保証されました。
設計判断
rendered のリセットを実現するために選択された手段は、@rendered.clear に依存しない新しいインスタンス生成です。content_class が任意のクラスであることを考慮し、clear メソッドの有無を前提としない設計とすることで、ユーザーが独自クラスを提供した際でも安全に動作します。このアプローチは、既存の content_class API 互換性を保ちつつ、メモ化による副作用を回避できる点で合理的です。
代替案としては、毎回新しいオブジェクトを生成するだけでなく、clear を呼び出す従来方式を残すことも考えられましたが、メモ化オブジェクトは String のサブクラスであり clear が未実装の場合に例外が発生するリスクがありました。そのため、_reset_rendered による明示的な再生成が最も安全かつシンプルな解決策と判断されました。さらに、テストコード側でも期待される振る舞いに合わせてアサーションを修正し、過去の互換性を壊さないよう配慮しています。
結果として、ドキュメントと実装の齟齬が解消され、テストケースが期待通りに動作するようになりました。この変更は既存の API 互換性を保ちつつ、将来的なカスタム content_class の拡張も容易にする設計的配慮が反映されています。
まとめ
ActionView::TestCase#render が rendered を正しくリセットするよう修正されたことで、公式ドキュメントが示す 「最後の render の出力」 という振る舞いが再び保証されました。_reset_rendered の導入とレンダリング状態フラグ @_rendering の追加により、メモ化導入後の副作用を排除しつつ、ユーザー定義クラスへの柔軟な対応も可能となります。これによりテストの信頼性が向上し、Rails のビュー検証基盤が一層堅牢になりました。