Symbol ストリーム名をリテラルとして扱うテストアサーションの統一

rails/rails

ActionCable のテストアサーションで Symbol が文字列と同等に扱われず、偽陰性や偽陽性が発生していた問題を解消します。変更は ActionCable::Channel::TestCase に型変換ロジックを追加し、API とテストの整合性を高めます。

背景

stream_from(:my_room) が実行時に String に変換 され、リテラルストリーム名 "my_room" として扱われることは既に実装で保証されています。にもかかわらず、テストアサーションの assert_broadcastsassert_has_stream が Symbol を文字列に統一せず、期待するストリームと不一致になるケースがありました。たとえば assert_broadcasts(:my_room, 1) は実際にブロードキャストが送信されても 0 と報告し、assert_has_no_stream(:my_room) はストリームが存在しても成功してしまうという false‑green が発生していました。これらは ActionCable の実運用 API とテストコードだけが食い違う例であり、テスト結果の信頼性を低下させる要因でした。

技術的な変更

broadcasting_for の拡張

ActionCable::Channel::TestCase#broadcasting_for が Symbol をそのまま返すようロジックを追加し、String と同様にリテラルストリーム名へ変換できるようにしました。

@@
-            return stream_or_object if stream_or_object.is_a?(String)
+            return stream_or_object if stream_or_object.is_a?(String) || stream_or_object.is_a?(Symbol)

この変更により、assert_broadcastsassert_broadcast_on が内部で呼び出す broadcasting_for が Symbol を正しく処理し、期待通りのストリームを参照できるようになります。

assert_has_stream / assert_has_no_stream の文字列化統一

assert_has_streamassert_has_no_stream でも引数を明示的に String(stream) に変換し、比較対象をストリーム名の文字列表現に統一しました。

def assert_has_stream(stream)
  check_subscribed!
  stream = String(stream)
  assert subscription.stream_names.include?(stream), "Stream #{stream} has not been started"
end

def assert_has_no_stream(stream)
  check_subscribed!
  stream = String(stream)
  assert subscription.stream_names.exclude?(stream), "Stream #{stream} has been started"
end

テストケースの追加

新しいシンボルストリームに対するテストが追加され、修正の効果が検証されています。

class SymbolStreamsTestChannel < ActionCable::Channel::Base
  def subscribed
    stream_from :my_room
  end
end

class SymbolStreamsTestChannelTest < ActionCable::Channel::TestCase
  def test_assert_has_stream_with_symbol_broadcasting
    subscribe
    assert_equal ["my_room"], subscription.stream_names
    assert_has_stream :my_room
  end

  def test_assert_has_no_stream_with_symbol_broadcasting
    subscribe
    assert_raises(Minitest::Assertion) { assert_has_no_stream :my_room }
  end
end

設計判断

後方互換性の確保を最優先し、メソッドシグネチャや公開 API は変更していません。シンボルを文字列へ明示的に変換するだけなので、既存コードが assert_broadcasts(:my_room) を使用していても動作に差異は生じません。さらに、一貫した型変換戦略をテストアダプタ層(#57698)とテストケース層の両方に適用したことで、スタック全体で同一ロジックが共有され、今後のメンテナンスコストが低減します。最小限の侵入で実装されたため、他の ActionCable 機能への副作用リスクも低く抑えられました。

まとめ

この PR は Symbol と String のストリーム名取り扱いを統一し、テストアサーションが本番 API と食い違う問題を根本から解消します。型変換のみのシンプルな変更で false‑negative/false‑positive を防止し、ActionCable のテスト信頼性と保守性を向上させました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
92106ee0

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

品質レビュー結果

Review Status:
リトライ後承認
Review Count:
2回 (改善を経て承認)
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文が要旨を簡潔に述べ、背景・技術的変更・設計判断・まとめという流れで構成されており、総論→各論→結論の形が明確です。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きハイライトは正しい形式です。しかし PR リンクが `[PR #57916](URL)` と記述されており、要求された `[#57916](URL)` 形式と完全には合致していません。

対象読者への適合性 ✓ PASS

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

内容は ActionCable のテストアサーションに詳しいエンジニア向けで、初心者向けの余計な説明はなく適切です。

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

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

各セクションは総論・各論・結論の段落構成になっており、1段落はトピックセンテンスで始まり、1トピックに絞られ、長さも適切で空行で区切られています。

Diff内容との照合 ⚠ WARNING

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

記事中の diff ブロックは実際の変更と一致しています。`broadcasting_for` の変更と `assert_has_stream`/`assert_has_no_stream` の文字列化は正確です。ただし、テストケースの追加部分のうち `SymbolBroadcastTestChannel` 以降が抜けており、Diff の全容を網羅していません。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

Symbol、String、broadcast、assert などの用語は PR と一致し、誤用はありません。

説明の技術的正確性 ✓ PASS

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

技術的な主張は PR の説明と合致し、因果関係も論理的です。

事実の突合 ✓ PASS

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

記事の全ての事実は PR のタイトル、説明、Diff で裏付けられており、推測や捏造は見当たりません。

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

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

PR 番号 #57916 以外に数値的な記述はなく、誤りはありません。

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

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

記事タイトルは PR タイトルの意味を日本語で正確に伝えており、内容と一致しています。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程など、PR に記載されていない外部知識は含まれていません。

時間表現の正確性 ✓ PASS

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

時間表現は使用されておらず、矛盾はありません。