インテグレーションテストに `query:` と `body:` キーワード引数を追加し、GET+JSON問題を修正

rails/rails

インテグレーションテストの process メソッドに query:body: キーワード引数が追加され、リクエストパラメータの送信先を明示的に制御できるようになりました。従来の params: + as: :json + GETの組み合わせで発生していたAPIオンリーアプリの障害が根本的に解消されます。

背景

params: キーワード引数は、GETリクエストに as: :json を組み合わせた場合に曖昧な挙動を示していました。GETリクエストでパラメータをJSONエンコードして送信するには、本来はクエリ文字列に含めるのが適切ですが、リクエストボディにJSONを乗せる実装との兼ね合いで、従来コードはGETを POST に変換してから X-Http-Method-Override: GET ヘッダを付与するという回避策を採っていました。

このハックは #57131 で報告されたように、Rack::MethodOverride ミドルウェアを除外するAPIオンリーアプリでは機能しません。Rack::MethodOverride がなければ X-Http-Method-Override ヘッダは無視され、ルーターにはPOSTリクエストとして到達します。GET /items というルートに対して [POST] "/items": No route matches というルーティングエラーが発生し、テストが意図せず失敗していました。

この問題の本質は、params: の意味論があいまいだったことにあります。テストヘルパーが「HTTPメソッドに応じてパラメータの送信先を推測する」設計になっていたため、JSONエンコードとGETメソッドが衝突した際に予期しない副作用が生じていました。

技術的な変更

process メソッドのシグネチャに query:body: の2つのキーワード引数が追加され、GETをPOSTに変換していたコードが削除されました。

変更前:

def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil)
  request_encoder = RequestEncoder.encoder(as)
  headers ||= {}

  if method == :get && as == :json && params
    headers["X-Http-Method-Override"] = "GET"
    method = :post
  end
  # ...
end

変更後:

def process(method, path, params: nil, headers: nil, env: nil, xhr: false, as: nil, query: nil, body: nil)
  request_encoder = RequestEncoder.encoder(as)
  headers ||= {}

  if query
    query_string = query.is_a?(String) ? query : Rack::Utils.build_nested_query(query)
    path = path.include?("?") ? "#{path}&#{query_string}" : "#{path}?#{query_string}"
  end

  # ...

  body_params = body || (method != :get ? params : nil)
  query_params = body ? nil : (method == :get ? params : nil)
end

各キーワード引数の役割は以下の通りです:

  • query:: HTTPメソッドに関わらず常にURLクエリ文字列に付加する。Hashまたはエンコード済みStringを受け付ける
  • body:: HTTPメソッドに関わらず常にリクエストボディに as: でエンコードして送信する
  • params:: 従来通りの挙動を維持。GETはクエリ文字列、それ以外はボディ

query: が指定された場合は Rack::Utils.build_nested_query でクエリ文字列に変換し、既存のクエリ文字列があれば & で連結、なければ ? で始まる文字列としてパスに付加します。また body: が明示された場合、params:query_params の計算から除外されるため、body:params: の意図しない二重送信を防ぎます。

テストでも変更の効果が確認できます。従来は POST メソッドと X-Http-Method-Override: GET ヘッダを期待していたアサーションが、GET メソッドかつ X-Http-Method-Override ヘッダなし(assert_nil)に書き換えられています。

# 変更前
assert_equal "POST", request.method
assert_equal "GET", request.headers["X-Http-Method-Override"]

# 変更後
assert_equal "GET", request.method
assert_nil request.headers["X-Http-Method-Override"]

設計判断

明示的な制御手段を追加し、暗黙的な変換を廃止するという方針が採られました。

従来の X-Http-Method-Override ハックは、「テストヘルパーがHTTPメソッドを変換する」という副作用をテストコードから隠蔽していました。テストを書くエンジニアからは、GETを意図したコードが実際にはPOSTとしてルーターに到達していることが見えず、APIオンリーアプリとの相性問題が顕在化するまで問題に気付きにくい設計でした。query:body: という明示的なキーワード引数を導入することで、「何を何処に送るか」がコードを読むだけで判断できます。

params: の後方互換性は完全に維持されており、既存のテストコードを変更する必要はありません。新しい query:body: はオプトイン形式のため、段階的な移行が可能です。PRのベンチマークによると、GETに as: :json を組み合わせた場合は従来比約6.6%のスループット向上も確認されています。これはPOST変換を省いたことによる経路の短縮が寄与しています。

まとめ

本PRは、テストヘルパーが持っていた「暗黙のHTTPメソッド変換」という設計上の負債を query: / body: の導入によって解消しました。Rack::MethodOverride の有無に依存しない実装になったことで、APIオンリーアプリを含むあらゆる構成でインテグレーションテストが正しく動作するようになります。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
fa30069f

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

「総論→各論→結論」の構成が記事全体と各セクションで明確に適用されています。リード文、背景、技術的変更、設計判断、まとめの各要素が適切に配置されており、論理的な流れが構築されています。

カスタムMarkdown構文 ✓ PASS

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

ファイル名付きシンタックスハイライト(```ruby:path/to/file.rb)とGitHubのIssue/PRリンク記法([#123](URL))が正しく使用されています。

対象読者への適合性 ✓ PASS

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

RailsのインテグレーションテストやHTTPリクエストに関する専門知識を持つエンジニアを対象としており、過度な説明がなく、技術レベルは適切です。

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

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

各セクションが総論→各論の構造を持ち、各段落はトピックセンテンスで始まり、1段落1トピックの原則が守られています。段落の長さも適切で、可読性が高いです。

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードブロック(変更前・変更後)は、提供されたDiff情報と正確に一致しています。ファイルパスも正しく記載されています。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Rack::MethodOverride」、「X-Http-Method-Override」、「キーワード引数」などの技術用語が正確かつ適切な文脈で使用されています。

説明の技術的正確性 ✓ PASS

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

GETリクエストがPOSTに変換される仕組みや、APIオンリーアプリで問題が発生する理由など、技術的な説明はDiffとPRの記述に基づいており、正確で論理的です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのDescriptionやDiff内容で裏付けられています。特にベンチマークの数値に関する言及はPR情報を正確に反映しており、ハルシネーションは見られません。

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

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

PR番号(#57140)、Issue番号(#57131)、パフォーマンス向上率(6.6%)など、記事に含まれる数値や固有名詞はすべて正確です。

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

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

記事のタイトルはPRの主題である「GET+JSON+paramsの問題修正」を的確に表現しており、解決策である「query:とbody:の追加」も含まれていて分かりやすいです。

外部知識の正確性 ✓ PASS

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

PR情報に記載のないバージョンサポート状況やリリース日程などの外部知識は含まれておらず、すべての情報が提供された資料に基づいています。

時間表現の正確性 ✓ PASS

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

「従来発生していた」「解消されます」など、過去の問題と今回の変更による解決という時間的な前後関係が正しく表現されています。