ActionView::TestCase#render が rendered をリセットするよう修正

rails/rails

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?

さらに、インスタンス変数 @_renderingview の状態リストに追加し、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#renderrendered を正しくリセットするよう修正されたことで、公式ドキュメントが示す 「最後の render の出力」 という振る舞いが再び保証されました。_reset_rendered の導入とレンダリング状態フラグ @_rendering の追加により、メモ化導入後の副作用を排除しつつ、ユーザー定義クラスへの柔軟な対応も可能となります。これによりテストの信頼性が向上し、Rails のビュー検証基盤が一層堅牢になりました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
335c6f79

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文、背景、技術的変更、設計判断、まとめの5セクションが揃っており、総論→各論→結論の流れが明確です。

カスタムMarkdown構文 ⚠ WARNING

シンタックスハイライト・GitHubリンク記法の正確性

コードブロックのファイル名付きシンタックスハイライトは正しい形式です。しかし PR リンクが `[PR #56422](URL)` となっており、求められる `[ #56422 ](URL)` の形式と異なります。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Rails の内部実装に関する記述が中心で、専門エンジニア向けの内容です。初心者向けの余計な説明はありません。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションは総論→各論→結論の構成で、段落はトピックセンテンスで始まり1段落1トピック、長さも適切です。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事中のコードブロックは提供された Diff と完全に一致しており、変更点が正確に反映されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

使用されている用語は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

技術的説明は Diff と PR の記述に裏付けられ、因果関係も論理的です。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事の全ての主張は PR の情報や Diff に基づいており、捏造や推測はありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR 番号 #56422 等の数値は正確です。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事タイトルは PR タイトル「ActionView::TestCase#render resets rendered」を日本語で適切に表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

バージョンサポートやリリース日程等の外部知識は含まれていません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

時間表現の歪曲はありません。