Reactテスト設計で迷う実務判断|レビューしやすい粒度と失敗例

Reactテスト設計のレビュー観点とテスト粒度を示す図

Reactのテストは「何を保証するか」から設計する

Reactテスト設計でまず決めたいのは、コンポーネントの内部実装ではなく、画面として何を保証するかです。useStateの値や関数呼び出し回数だけを追うと、実装変更に弱いテストになります。そのため、実務ではユーザー操作、表示結果、状態遷移、API失敗時の振る舞いを中心に置くほうがレビューしやすくなります。

もちろん、すべてをE2Eテストで確認すればよいわけではありません。実行時間、保守コスト、失敗時の原因特定を考えると、Reactのテストは複数の粒度に分ける必要があります。この記事では、5年目以上のエンジニアがレビューで見たい判断基準に絞って整理します。

Reactテスト設計でよくある失敗例

実装詳細に密着しすぎる

よくある失敗は、コンポーネントの内部関数やstateの形をテストの中心にすることです。例えば、ボタンを押した結果を確認せず、特定のハンドラが呼ばれたかだけを見るテストは危険です。リファクタリングで関数名や分割方法が変わると、画面の振る舞いが同じでもテストが落ちます。

そこで、レビューでは「ユーザーから見える変化を検証しているか」を確認します。React Testing Libraryの考え方も、ユーザーに近い操作と問い合わせを重視します。詳しくはReact Testing Libraryの公式ドキュメントが参考になります。

スナップショットに責務を持たせすぎる

スナップショットテストは、差分の検知には便利です。一方で、仕様の保証には向いていない場面があります。大きなコンポーネントを丸ごと保存すると、差分が出てもレビューアは意味を判断しづらくなります。その結果、意図を確認しないまま更新されるテストになりがちです。

使う場合は、静的な表示部品や意図が明確な小さな差分に限定します。重要な業務ルール、権限による表示切り替え、入力エラーは、明示的なアサーションで確認したほうが安全です。

モックが現実のAPIから離れる

APIレスポンスのモックが古いまま残ると、テストは通っているのに本番で画面が壊れます。特にReact/TypeScript案件では、nullableな項目、空配列、権限不足、ページングの終端が抜けやすいです。そのため、モックは成功ケースだけでなく、業務上起こり得る境界条件を含めます。

失敗例起きる問題レビューで見る点
正常系だけをテストする空状態や権限エラーで崩れる業務上の境界条件があるか
実装関数を直接検証するリファクタリングで壊れる画面の振る舞いを見ているか
巨大なスナップショットに頼る差分の意味が読めない明示的な期待値があるか
APIモックが雑本番データで不具合が出る契約と境界値が反映されているか

テスト粒度はリスクと変更頻度で決める

Reactテスト設計では、単体、統合、E2Eを機械的に分けてもあまり意味がありません。まず、壊れたときの影響が大きい機能を見ます。次に、変更頻度が高いUIと、変更頻度が低い業務ルールを分けます。これにより、テストの置き場所が決めやすくなります。

  • フォーム送信、権限表示、金額計算などは統合寄りに確認する
  • 純粋な変換関数やバリデーション関数は単体テストで確認する
  • 主要導線やブラウザ依存の挙動はE2Eで確認する
  • 見た目だけの小さな部品は、Storybookや目視レビューと分担する

ただし、統合寄りのテストを増やすほど実行時間は伸びます。CIで毎回全件を流すのか、変更対象に応じて分けるのかも設計の一部です。Vitestを使う場合は、実行速度やwatchのしやすさを活かしつつ、ブラウザ差分の検証をE2Eに寄せます。設定や実行方法はVitest公式ガイドを確認するとよいでしょう。

レビューで確認したいReactテスト設計の観点

ユーザーの言葉で検証しているか

まず、テストがrole、label、textなど、利用者に近い問い合わせで書かれているかを見ます。class名やDOM構造に依存したテストは、CSSやマークアップ変更で落ちやすいです。一方で、アクセシブルな名前で問い合わせるテストは、UI品質のレビューにもつながります。

なお、React公式ドキュメントでもUIを純粋に保つ設計や副作用の扱いが説明されています。テストしやすいコンポーネント設計を考えるうえで、Keeping Components Pureは確認しておきたい内容です。

非同期処理の完了条件が明確か

API取得、debounce、ローディング表示を含む画面では、非同期の待ち方が重要です。単にsetTimeoutで待つテストは不安定になります。そのため、表示されるべき結果、消えるべきローディング、出るべきエラーメッセージを基準に待ちます。

例えば、検索画面なら「入力後にAPIが呼ばれた」だけでは足りません。検索結果が表示されること、0件時の文言が出ること、通信失敗時に再試行できることまで見ると、仕様の抜けが減ります。

テストデータが仕様を説明しているか

テストデータは、単なるダミーではありません。レビューアが仕様を理解するための材料です。例えば、管理者、一般ユーザー、停止中ユーザーを並べると、権限表示の意図が読みやすくなります。逆に、user1、user2だけでは判断できません。

さらに、Factoryやfixtureを使う場合も注意が必要です。共通データを隠しすぎると、テストごとの差分が見えません。重要な条件はテスト内で明示し、共通化は読みやすさを壊さない範囲に留めます。

テストしやすいReactコンポーネント設計

テストが書きづらい場合、テストコードだけで解決しようとしないほうがよいです。多くの場合、コンポーネントの責務が広すぎます。画面表示、データ取得、整形、権限判定、フォーム制御が一箇所に集まると、テストも大きくなります。

そこで、業務ルールは関数やCustom Hookに分けます。UIコンポーネントは、表示と操作に集中させます。ただし、分ければよいという話でもありません。小さすぎる分割は追跡コストを増やします。変更理由が違うものを分ける、という基準で十分です。

// 良い判断例: UIから独立した業務ルールを先にテストする
export function canSubmitOrder(input: OrderInput): boolean {
  return input.items.length > 0 && input.agreedToTerms && !input.isSubmitting;
}

// コンポーネント側では、ユーザー操作と表示結果を確認する
// 「送信ボタンが無効になる条件」が仕様として読みやすくなる

この形にすると、業務ルールの単体テストと、画面の統合テストを分けられます。その結果、失敗時の原因も追いやすくなります。

Reactテスト設計の判断チェックリスト

  • そのテストは、ユーザーが困る不具合を検知できるか
  • 実装詳細ではなく、画面の振る舞いを確認しているか
  • 正常系だけでなく、空状態、失敗、権限差分を含んでいるか
  • テスト名とデータから、仕様の意図が読めるか
  • CIで落ちたときに、原因を短時間で切り分けられるか
  • レビューで「なぜこの粒度か」を説明できるか

最後に重要なのは、テスト数ではなく信頼度です。テストが多くても、仕様変更のたびに大量修正が必要なら開発速度を落とします。一方で、重要な導線に絞ったテストは、少ない数でもレビューとリリース判断を支えます。

よくある質問

Reactのテストはどこまで書くべきですか?

まず、業務影響が大きい画面から書くのが現実的です。ログイン後の主要導線、入力フォーム、権限表示、エラー表示は優先度が高いです。逆に、単純な表示だけの部品は、共通コンポーネントやStorybookの確認に寄せてもよい場合があります。

単体テストとE2Eテストの比率はどう考えますか?

固定比率で決めるより、変更頻度と失敗時の影響で決めます。業務ルールは単体テストで素早く確認します。複数コンポーネントをまたぐ表示やフォーム送信は統合寄りに見ます。そして、ログインから完了までの主要導線はE2Eで薄く押さえると、保守コストを抑えやすくなります。

まとめ

Reactテスト設計では、実装詳細ではなく、ユーザーに見える振る舞いを保証することが重要です。また、テスト粒度はリスク、変更頻度、原因特定のしやすさで決めます。さらに、レビューではテストデータ、非同期処理、モックの現実性まで確認すると、壊れにくいテストになります。

React/TypeScript案件では、テスト設計の考え方がチームのレビュー品質やリリース判断に直結します。現在の案件でテスト方針、レビュー観点、担当領域の広げ方に迷っている場合は、案件選びや技術スタックの相談材料として整理してみるのも有効です。

IaC INP PM PMO PMP UX Webディレクター インフラエンジニア キャリアチェンジ フロントエンドエンジニア