accelint-react-testing について
accelint-react-testingは、Testing LibraryでReactコンポーネントのテストを書く、またはレビューする開発者向けのスキルです。アクセシブルなクエリ、実際のユーザー操作に近いイベント、画面上の振る舞いの検証を重視し、参照資料と監査スクリプトも備えています。
主な機能
- 01アクセシブルなクエリ選択
- 02userEventによる操作ガイド
- 03非同期クエリと待機方法
- 04Provider付きカスタムレンダーの指針
- 05テストのアンチパターン監査スクリプト
活用シーン
- 01ユーザー中心のReactコンポーネントテスト作成
- 02テストのアクセシビリティ問題のレビュー
- 03クエリやイベント処理のアンチパターン監査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ gohypergiant/ agent-skills --skill accelint-react-testingインストール後、Claude Code で/accelint-react-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
AGENTS.mdREADME.mdassets/custom-render-template.tsxoutput-report-template.mdreferences/accessibility-queries.mdanti-patterns.mdasync-testing.mdcustom-render.mdquery-priority.mdquery-variants.mduser-events.mdscripts/check-query-priority.shdetect-wrapper-queries.shfind-fire-event.sh詳細
React テストのベストプラクティス
Testing Library を使用して、保守しやすくユーザー中心の React コンポーネントテストを書くための専門的なガイダンスです。クエリの選択、アクセシビリティを優先したテスト、実装の詳細に依存しないことに重点を置いています。
React テストを書く際に絶対にしてはいけないこと
- アクセシブルなクエリを試す前に、テスト ID で検索することは絶対に避けてください - テスト ID はアクセシビリティの検証を回避します。
data-testid="submit"が付いていてもアクセシブルな名前のないボタンはテストに通りますが、スクリーンリーダーのユーザーには機能しません。テスト ID でテストに通ると、アクセシブルでない UI をリリースすることになります。クエリの優先順位は、getByRole>getByLabelText>getByText>getByTestIdです。このリストを下に進むほど、UI が使いやすいという確信が薄れます。 userEventが使える場合、ユーザー操作にfireEventを使うことは絶対に避けてください -fireEventは単一の DOM イベントを発生させるため、実際のユーザーが発生させる一連のイベントを再現できません。fireEvent.click()が発生させるのはクリックイベント 1 つですが、実際のユーザー操作では、フォーカス → マウスダウン → マウスアップ → クリックが発生します。fireEventで動作するコンポーネントでも、ユーザーが通常どおり操作すると本番環境で問題が起きます。userEvent.click()は一連の操作をすべてシミュレートするため、fireEventでは見つけられないバグを検出できます。- ユーザーの振る舞いではなく、実装の詳細をテストすることは絶対に避けてください - 「状態変数 X が Y に等しい」や「関数 Z が呼び出された」と検証するテストは、誤った失敗を招きます。
useStateからuseReducerにリファクタリングすると、UI がまったく同じように動作するにもかかわらず、すべてのテストが失敗します。実装の詳細をテストするとリファクタリングが難しくなり、ユーザー体験が機能するという確信も得られません。コンポーネント内部でどのように実現しているかではなく、ユーザーが見たり操作したりする内容(レンダリングされた出力や操作の結果)をテストしてください。 - 初回のレンダリング後に、
containerからクエリしたり、分割代入したクエリを使ったりすることは絶対に避けてください -const { getByText } = render(<Component />)のようにすると、更新を見逃す古いクエリが作られます。状態の変化後も、分割代入したクエリは初期 DOM のスナップショットを検索するため、新たにレンダリングされた要素を見つけられません。その結果、実際には要素が存在するのに「element not found」エラーが発生します。常にscreen.getByText()を使ってください。現在の DOM の状態が自動的に検索されます。また、screenを一貫して使えば、クエリを追加する際に分割代入の更新が不要になるため、テストの保守性も高まります。 - テストのためだけに
aria-labelやrole属性を追加することは絶対に避けてください - テストで要素を見つけられるようにaria-label="submit-button"やrole="button"を追加しているなら、やり方が逆です。テストでは、コンポーネントがすでにアクセシブルであることを検証すべきであり、テストのためにアクセシブルにするべきではありません。テスト専用の ARIA を追加すると、本番コードが不要に複雑になり、実際のアクセシビリティの問題が隠れてしまいます。まず、コンポーネントのセマンティックな HTML と既存の ARIA を修正してください。 - 具体的なアサーションなしに、コンポーネントツリー全体のスナップショットを取ることは絶対に避けてください - 500 行を超えるような大規模なスナップショットは、クラス名の更新、新しいプロパティの追加、要素の並べ替えなど、どんな変更でも失敗します。その結果、レビュアーは内容を適切に評価できない差分を承認することになります。テストの失敗時に理由を理解せず「スナップショットを更新するだけ」で済ませるのであれば、そのテストに価値はありません。エラーメッセージやデータテーブルなど、重要な構造に絞ってスナップショットを取り、それ以外には対象を絞ったアサーションを使ってください。
- Promise を返す処理に
waitForを使うことは絶対に避けてください -waitFor(() => expect(element).toBeInTheDocument())はタイムアウトまで繰り返しポーリングしますが、Promise ベースのfindByクエリを使えば一度で解決できます。await screen.findByText('loaded')はポーリングせずに要素の出現を待ちます。要素が消えることの確認や属性の変更待ちなど、findByを使えないアサーションに限ってwaitForを使ってください。 waitForのコールバック内で副作用を実行することは絶対に避けてください -waitFor(() => { fireEvent.click(button); expect(text).toBeInTheDocument(); })のようにすると、waitForの再試行のたびにクリックが実行され、予測できない動作を引き起こします。waitForはアサーションを待つためのものであり、操作を実行するためのものではありません。操作はすべてwaitForの外で行い、その後、アサーションにのみwaitForを使ってください。例:fireEvent.click(button); await waitFor(() => expect(text).toBeInTheDocument());。さらによい方法は、await userEvent.click(button); expect(await screen.findByText(text)).toBeInTheDocument();です。- プロバイダーの要件を説明せずにカスタムレンダーを作ることは絶対に避けてください - 必要なストアの構造が説明されていない
renderWithRedux関数を作ると、すべての開発者が困ります。renderWithRedux()ではなくrender(<Component />)を呼び出すと、原因が分かりにくい「Cannot read property of undefined」エラーでテストが失敗し、デバッグに 15 分も費やすことになります。テストユーティリティでプロバイダーのセットアップを一元化し、適切な使い方を強制する TypeScript の型を用意するか、必要なラッパーを目立つ場所に記載してください。 - 異なる Testing Library のインポート元からクエリを混在させることは絶対に避けてください -
@testing-library/reactのrenderと@testing-library/domのクエリを両方インポートすると、混乱を招きます。React パッケージのscreenは DOM パッケージのgetByRoleでは動作せず、「screen.getByRole is not a function」エラーが発生します。React コンポーネントには、すべてのクエリを@testing-library/reactからインポートしてください。React 固有の拡張機能とともに、DOM パッケージのすべての機能が再エクスポートされています。
テストを書く前に確認すること
React コンポーネントのテストを実装する前に、以下の考え方を適用してください。
クエリ選択の方針
- ユーザーはこの要素をどのように見つけるでしょうか? 実際のユーザーはテスト ID や CSS クラスではなく、ラベル、ボタン、見出しを探します。ロールやラベルでクエリできない場合、UI のアクセシビリティに問題があります。クエリの難しさから、本番環境に到達する前に UX の問題を見つけられます。
- この要素を見つける必要は本当にありますか? すべての要素にクエリのアサーションが必要なわけではありません。ユーザーは「ローディングスピナーが存在する」ことではなく、「読み込み後にデータが表示される」ことを確認します。中間状態ではなく、結果をテストしてください。
- getBy、queryBy、findBy のどれを使うべきでしょうか? すぐに存在する要素には getBy から始めてください。最もわかりやすいエラーメッセージが表示されます。queryBy は要素が存在しないことをアサートする場合(.not.toBeInTheDocument())にのみ使ってください。非同期で表示される要素には findBy を使います。queryBy と expect(...).toBeInTheDocument() の組み合わせは使わず、要素がない場合によりわかりやすいエラーメッセージが表示される getBy を使ってください。
ユーザー視点と実装のテスト
- ユーザーなら、この機能が動作することをどのように確認するでしょうか? ユーザーはボタンをクリックし、テキストを読みます。状態変数やモック関数の呼び出しは確認しません。テストで
rerender()を使ったり、コンポーネントの内部にアクセスしたりする場合は、実装をテストしています。ユーザー操作を通じてテストするように見直してください。 - 動作を変えないリファクタリングをしても、このテストは通りますか? 関数名の変更や useState から useReducer への切り替えでテストが失敗するなら、実装の詳細をテストしています。このようなテストは、安全な変更を妨げて時間を浪費する一方で、UI が実際に動作するという確信を与えません。
非同期処理とタイミング
- このクエリの対象は非同期で読み込まれますか? useEffect、API 呼び出し、または setTimeout を介して読み込まれるものには
findBy*を使ってください。getBy*は要素がないとすぐにエラーを投げますが、findBy*は要素が表示されるまで待機します。非同期コンテンツに getBy を使うと、本番環境でのみ失敗する競合状態が発生します。 - 要素が表示されるのを待っていますか、それとも消えるのを待っていますか? 表示には
findBy*クエリを使います。消失にはwaitForElementToBeRemovedを使います。状態の変化には、アサーションを伴うwaitForを使います。それぞれ意味が異なるため、誤ったものを使うとテストが不安定になったり、タイムアウトが長くなったりします。
テストの分離とセットアップ
- このコンポーネントのレンダリングにはコンテキストプロバイダーが必要ですか? useContext、Redux フック、または React Router を使うコンポーネントは、プロバイダーがないとエラーになります。必要なプロバイダーでコンポーネントを自動的にラップするカスタムのレンダー用ユーティリティを作成してください。すべてのテストファイルでプロバイダーのセットアップを繰り返すと、保守が非常に困難になります。
- このテストケースに必要な最小限のセットアップは何でしょうか? 過剰なセットアップ(ボタンのテストで関数を 10 個モックするなど)を伴うテストは、不安定で時間もかかります。モックするのは外部依存関係(API、localStorage)のみにし、独自の関数はモックしないでください。セットアップが複雑なら、コンポーネントの設計に問題がある可能性があります。
使用方法
このスキルでは、コンテキストの使用量を抑えるために段階的開示を採用しています。
1. 概要(AGENTS.md)から始める
すべてのルールを簡潔にまとめた概要を確認するには、AGENTS.md を読んでください。
2. 必要に応じて特定のルールを読み込む
次の明示的なトリガーを参考に、各リファレンスファイルを読み込むタイミングを判断してください。
必須の読み込み(ファイル全体を読み込む):
- クエリ(getBy、findBy、queryBy*)を記述する場合** → query-priority.md
- ユーザー操作(クリック、入力など)をシミュレートする場合 → user-events.md
次のパターンが見られる場合に読み込む:
- getBy と findBy と queryBy の使い分けに迷った場合 → query-variants.md
- waitFor、非同期クエリ、または「act」の警告がある場合 → async-testing.md
- Context、Redux、Router を使用するコンポーネントの場合 → custom-render.md
- アクセシビリティまたは ARIA 属性をテストする場合 → accessibility-queries.md
- container、wrapper、または rerender を多用する場合 → anti-patterns.md
- クエリが失敗する、または適切なセレクターが見つからない場合 → screen.debug() の使い方について query-variants.md
特に必要な場合を除き、読み込まない:
- プロバイダーを使わない単純なコンポーネントでは、custom-render.md を読み込まないでください
- 同期テストでは、async-testing.md を読み込まないでください
- ARIA やアクセシビリティ上の懸念をテストする場合を除き、accessibility-queries.md を読み込まないでください
3. パターンを適用する
各リファレンスファイルには、次の内容が含まれています。
- ❌ アンチパターンを示す不適切な例
- ✅ 最適な実装を示す適切な例
- そのパターンが重要な理由の説明
4. 既存のテストを監査する(任意)
提供されているスクリプトを使用して、既存のテストスイートを監査します。
# Check query priority (testId usage, container.querySelector)
./scripts/check-query-priority.sh
# Find fireEvent that should be userEvent
./scripts/find-fire-event.sh
# Detect deprecated wrapper/container patterns
./scripts/detect-wrapper-queries.sh
5. レポートテンプレートを使用する
このスキルをテストコードレビューに使用する場合は、標準化されたレポート形式を使用してください。
テンプレート: assets/output-report-template.md
レポート形式には、次の内容が含まれています。
- アクセシビリティへの信頼度とユーザー中心のカバレッジ評価を含むエグゼクティブサマリー
- 優先順位付けのための重大度(Critical、High、Medium、Low)
- 影響分析(アクセシビリティへの信頼度、ユーザー中心の信頼度、テストの信頼性、リファクタリングの安全性)
- 分類(クエリの優先順位、クエリのバリエーション、ユーザーイベント、非同期テスト、カスタムレンダー、アクセシビリティ、アンチパターン)
- 詳細なガイダンスを記載した references/ へのリンクを含むパターンの参照
- すべての問題を追跡するためのサマリーテーブル
レポートテンプレートを使用する場合:
- スキルが
/accelint-react-testing <path>から直接呼び出された場合 - ユーザーが「テストコードをレビュー」または「テストを監査」と依頼し、スキルがファイル全体に対して暗黙的に呼び出された場合
レポートテンプレートを使用しない場合:
- ユーザーが「この関数のテストを書いて」と依頼した場合(直接実装)
- ユーザーが「このテストの何が問題?」と尋ねた場合(質問に回答)
- ユーザーが特定のテストの修正を依頼した場合(正式なレポートを作成せず、直接修正を適用)
このスキルの対象範囲
React Testing Library のパターンに関する専門的なガイダンス:
- クエリの優先順位 - getByRole から getByTestId までのアクセシブルなクエリ階層
- クエリのバリエーション - さまざまな状況で getBy、findBy、queryBy を使い分ける方法
- ユーザーイベント - 現実的な操作テストにおける userEvent と fireEvent の使い分け
- 非同期テスト - プロミス、waitFor、findBy クエリの処理と、act 警告の回避
- カスタムレンダー - 複雑なコンポーネントでプロバイダー(Context、Redux、Router)を設定する方法
- アクセシビリティクエリ - ロール、ラベル、ARIA 属性を使用したテスト
- アンチパターン - 実装の詳細、container の使用、過剰なスナップショットの回避
- 監査スクリプト - 既存のテストにある最適でないパターンを自動検出
クエリ選択の判断ツリー
クエリを選択する際は、この階層に沿って上から順に試してください。
1. getByRole ← Preferred: Accessible, reflects how users & ATs interact
↓ Can't find role?
2. getByLabelText ← For form fields: matches how users read forms
↓ No label?
3. getByPlaceholderText ← For inputs: less accessible than labels
↓ No placeholder?
4. getByText ← For non-interactive content: headings, paragraphs
↓ Text not unique?
5. getByDisplayValue ← For form inputs: current value
↓ No display value?
6. getByAltText ← For images: alt attribute
↓ No alt text?
7. getByTitle ← For title attribute: less accessible
↓ No title?
8. getByTestId ← Last resort: no accessibility verification
主な原則:
- より上位のクエリほど、アクセシビリティへの信頼度が高くなります
- ロールやラベルでクエリできない場合は、まずコンポーネントのアクセシビリティを修正してください
- getByTestId を使うのは、「ここでアクセシビリティを実現するのは不可能だと確認済み」という意味です
重要な注意事項
screenのエクスポートは魔法ではありません。単にgetQueriesForElement(document.body)を呼び出しているだけです。screen.getByRole()の使用は、renderから分割代入したgetByRole()と同じですが、screenは再レンダー後も古くなりません。- Testing Library は、アクセシブルな要素を最も簡単にクエリできるようにすることで、アクセシビリティを促進します。クエリが難しい場合、UI の使い勝手にも問題があります。クエリの難しさは、UX に関するコードの危険信号です。
- クエリが失敗したら、screen.debug() または screen.logTestingPlaygroundURL() を使います。getByRole が失敗した場合は、
screen.debug()を実行して現在の DOM を確認するか、screen.logTestingPlaygroundURL()を実行して、どのクエリが使えるかを確認できるインタラクティブなツールを表示します。セレクターを推測せず、Testing Library に利用可能なものを表示させてください。 - queryBy は null を黙って返すため、よりわかりやすいエラーが得られる getBy を使います。要素が存在するはずの場合、
getBy*は、類似する要素や利用可能なロールについての役立つ提案を含むエラーをスローします。queryBy*は null を返すため、役立つ情報が少ないエラー出力で独自のアサーションを追加する必要があります。要素が存在しないことを.not.toBeInTheDocument()で検証する場合に限り、queryBy を使ってください。 - Act の警告は、Testing Library が認識していないところで React の状態更新が行われたことを意味します。通常は、テスト完了後に Promise が解決した場合や、非同期クエリで
awaitを付け忘れた場合に発生します。findBy や waitFor を正しく使ったことが原因ではありません。 - userEvent のメソッドは非同期(Promise を返す)ですが、fireEvent のメソッドは同期的です。
await userEvent.click()を付け忘れると、アサーションの実行後に状態が更新され、「act」の警告が発生したり、テストが不安定になったりします。