react-testing-library
React Testing Libraryを使用したユーザー視点のコンポーネントテストを支援するスキルです。ロール・ラベル・テキストによる要素の取得、ユーザーイベントのシミュレーション、非同期UIの動作検証など、Reactコンポーネントのテスト作成時に活用できます。`@testing-library/react`や`user-event`を用いたアクセシビリティファーストなテスト実装に対応しています。
description の原文を見る
React Testing Library: user-centric component testing with queries, user-event simulation, async utilities, and accessibility-first API. Use when writing React component tests, selecting elements by role/label/text, simulating user events, or testing async UI behavior. Keywords: React Testing Library, @testing-library/react, user-event, queries, render.
SKILL.md 本文
React Testing Library Skill
クイックナビゲーション
| トピック | リンク |
|---|---|
| クエリ | references/queries.md |
| ユーザーイベント | references/user-events.md |
| API | references/api.md |
| 非同期 | references/async.md |
| デバッグ | references/debugging.md |
| 設定 | references/config.md |
インストール
インストール: npm install --save-dev @testing-library/react @testing-library/dom。推奨される追加パッケージ: @testing-library/user-event と @testing-library/jest-dom。React 19 は v16.1.0 以上が必要です。
コア哲学
「テストが実際のソフトウェアの使われ方に近いほど、より大きな信頼を得ることができます。」
テストしないもの:
- コンポーネントの内部状態
- 内部メソッド
- ライフサイクルメソッド
- 子コンポーネントの実装詳細
テストするもの:
- ユーザーが見て操作できるもの
- ユーザーの視点からの動作
- アクセシビリティ (role、label によるクエリ)
クエリの優先度
このような優先度でクエリを使用してください:
1. 全員にアクセス可能 (推奨)
// ベスト — ARIA role による
getByRole("button", { name: /submit/i });
getByRole("textbox", { name: /email/i });
// フォームフィールド — label による
getByLabelText("Email");
// 非対話的なコンテンツ — text による
getByText("Welcome back!");
2. セマンティッククエリ
// 画像
getByAltText("Company logo");
// Title 属性 (信頼性が低い)
getByTitle("Close");
3. テスト ID (エスケープハッチ)
// 他のクエリが機能しない場合のみ
getByTestId("custom-element");
クエリタイプ
| タイプ | マッチなし | 1 マッチ | >1 マッチ | 非同期 |
|---|---|---|---|---|
getBy... | throw | return | throw | No |
queryBy... | null | return | throw | No |
findBy... | throw | return | throw | Yes |
getAllBy... | throw | array | array | No |
queryAllBy... | [] | array | array | No |
findAllBy... | throw | array | array | Yes |
使い分け:
getBy*— 要素が存在するqueryBy*— 要素が存在しない可能性がある (expect(...).not.toBeInTheDocument()のようなアサーション用)findBy*— 要素が非同期で表示される
基本テストパターン
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test("shows greeting after login", async () => {
const user = userEvent.setup();
render(<App />);
// Act — ユーザーインタラクションをシミュレート
await user.type(screen.getByLabelText(/username/i), "john");
await user.click(screen.getByRole("button", { name: /login/i }));
// Assert — 結果を確認
expect(await screen.findByText(/welcome, john/i)).toBeInTheDocument();
});
ユーザーイベント
fireEvent の代わりに常に @testing-library/user-event を使用してください:
import userEvent from "@testing-library/user-event";
test("user interactions", async () => {
const user = userEvent.setup();
// クリック
await user.click(element);
await user.dblClick(element);
await user.tripleClick(element);
// テキスト入力
await user.type(input, "Hello");
await user.clear(input);
// 選択
await user.selectOptions(select, ["option1", "option2"]);
// キーボード
await user.keyboard("{Enter}");
await user.keyboard("[ShiftLeft>]a[/ShiftLeft]"); // Shift+A
// クリップボード
await user.copy();
await user.paste();
// ポインター
await user.hover(element);
await user.unhover(element);
});
非同期パターン
waitFor — 成功まで再試行
await waitFor(() => {
expect(screen.getByText("Loaded")).toBeInTheDocument();
});
// オプション付き
await waitFor(() => expect(callback).toHaveBeenCalled(), {
timeout: 5000,
interval: 100,
});
findBy — 組み込み waitFor
// 次と同等: await waitFor(() => getByText('Loaded'))
const element = await screen.findByText("Loaded");
waitForElementToBeRemoved
await waitForElementToBeRemoved(() => screen.queryByText("Loading..."));
一般的なパターン
プロバイダー付きカスタムレンダー
// test-utils.tsx
import { render } from "@testing-library/react";
import { ThemeProvider } from "./ThemeProvider";
import { AuthProvider } from "./AuthProvider";
function AllProviders({ children }) {
return (
<ThemeProvider>
<AuthProvider>{children}</AuthProvider>
</ThemeProvider>
);
}
const customRender = (ui, options) => render(ui, { wrapper: AllProviders, ...options });
export * from "@testing-library/react";
export { customRender as render };
フック のテスト
import { renderHook, act } from "@testing-library/react";
test("useCounter increments", () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
新しい props で再レンダー
const { rerender } = render(<Counter count={1} />);
expect(screen.getByText("Count: 1")).toBeInTheDocument();
rerender(<Counter count={2} />);
expect(screen.getByText("Count: 2")).toBeInTheDocument();
コンテナ内のクエリ
import { within } from "@testing-library/react";
const modal = screen.getByRole("dialog");
const submitBtn = within(modal).getByRole("button", { name: /submit/i });
デバッグ
// DOM 全体を出力
screen.debug();
// 特定の要素を出力
screen.debug(screen.getByRole("button"));
// 利用可能な role をログ出力
import { logRoles } from "@testing-library/react";
logRoles(container);
// prettyDOM オプション付き
screen.debug(undefined, 10000); // max length
jest-dom マッチャー
import "@testing-library/jest-dom";
expect(element).toBeInTheDocument();
expect(element).toBeVisible();
expect(element).toBeEnabled();
expect(element).toBeDisabled();
expect(element).toHaveTextContent("Hello");
expect(element).toHaveValue("input value");
expect(element).toHaveAttribute("href", "/home");
expect(element).toHaveClass("active");
expect(element).toHaveFocus();
expect(element).toBeChecked();
設定
import { configure } from "@testing-library/react";
configure({
// カスタムテスト ID 属性
testIdAttribute: "data-my-test-id",
// 非同期タイムアウト
asyncUtilTimeout: 5000,
// デフォルト非表示
defaultHidden: true,
// 提案を throw する (デバッグ)
throwSuggestions: true,
});
❌ 禁止事項 (アンチパターン)
// ❌ class/id でクエリしない
container.querySelector(".my-class");
// ❌ container.firstChild を使わない
const { container } = render(<Component />);
expect(container.firstChild).toHaveClass("active");
// ❌ userEvent が機能する場合は fireEvent を使わない
fireEvent.click(button); // 代わりに userEvent.click を使用
// ❌ 実装詳細をテストしない
expect(component.state.loading).toBe(false);
// ❌ findBy と waitFor を組み合わせない
await waitFor(() => screen.findByText("x")); // findBy は既に待機します
// ❌ waitFor コールバック内でアサーションしない (必要な場合を除く)
await waitFor(() => {
expect(mockFn).toHaveBeenCalled(); // OK - 呼び出しを待つ必要があります
});
✅ ベストプラクティス
// ✅ 全クエリで screen を使用する
import { render, screen } from "@testing-library/react";
render(<Component />);
screen.getByRole("button"); // Good
// ✅ fireEvent より userEvent を優先
const user = userEvent.setup();
await user.click(button);
// ✅ 非同期要素には findBy を使用
const element = await screen.findByText("Loaded");
// ✅ 存在しないことをアサートするには queryBy を使用
expect(screen.queryByText("Error")).not.toBeInTheDocument();
// ✅ スコープ付きクエリには within を使用
const form = screen.getByRole("form");
within(form).getByLabelText("Email");
// ✅ アクセシブルクエリ (role、label、text) を使用
getByRole("button", { name: /submit/i });
TextMatch オプション
// 完全一致 (デフォルト)
getByText("Hello World");
// 部分文字列一致
getByText("llo Worl", { exact: false });
// 正規表現
getByText(/hello world/i);
// カスタム関数
getByText((content, element) => {
return element.tagName === "SPAN" && content.startsWith("Hello");
});
クイックリファレンス
| インポート | 用途 |
|---|---|
render | コンポーネントを DOM にレンダー |
screen | レンダー済み DOM をクエリ |
cleanup | コンポーネントをアンマウント (Jest で自動) |
act | 状態更新をラップ |
renderHook | カスタムフックをテスト |
within | クエリのスコープを要素に限定 |
waitFor | アサーション成功まで再試行 |
configure | グローバルオプションを設定 |
userEvent.setup() | ユーザーイベントインスタンスを作成 |
リンク
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- itechmeat
- リポジトリ
- itechmeat/llm-code
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/itechmeat/llm-code / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。