jpskill.com
🛠️ 開発・MCP コミュニティ

testing-library

ユーザーが操作するのと同じようにUIコンポーネントをテストするために、実装の詳細ではなく役割やテキスト、ラベルで要素を検索し、アクセシビリティも考慮したテストを効率的に行うSkill。

📜 元の英語説明(参考)

Test UI components the way users interact with them using Testing Library — query by role, text, and label instead of implementation details. Use when someone asks to "test React components", "Testing Library", "user-centric testing", "test accessibility", "test without implementation details", or "render and query components in tests". Covers React Testing Library, queries, user events, async testing, and accessibility assertions.

🇯🇵 日本人クリエイター向け解説

一言でいうと

ユーザーが操作するのと同じようにUIコンポーネントをテストするために、実装の詳細ではなく役割やテキスト、ラベルで要素を検索し、アクセシビリティも考慮したテストを効率的に行うSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o testing-library.zip https://jpskill.com/download/15473.zip && unzip -o testing-library.zip && rm testing-library.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/15473.zip -OutFile "$d\testing-library.zip"; Expand-Archive "$d\testing-library.zip" -DestinationPath $d -Force; ri "$d\testing-library.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して testing-library.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → testing-library フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

Testing Library

概要

Testing Library は、CSS クラスやテスト ID ではなく、アクセシブルなロール、テキストコンテンツ、またはラベルによって要素を見つけることで、ユーザーの視点から UI コンポーネントをテストします。ユーザーがボタンを見つけられない場合、テストもそれを見つけるべきではありません。このアプローチは、デフォルトでアクセシビリティの問題を捉え、リファクタリング(CSS クラスの名前を変更してもテストはパスする)に耐え、UI が実際に動作することを証明するテストを作成します。

使用する場面

  • React/Vue/Svelte コンポーネントのテスト
  • リファクタリングに耐えるテストが必要な場合(CSS セレクターや内部状態のチェックがない)
  • アクセシビリティ(ARIA ロール、ラベル)を検証する必要がある場合
  • ユーザーインタラクション(クリック、タイピング、フォーム送信)のテスト
  • コンポーネントの動作の統合テスト(スナップショットテストではない)

手順

セットアップ

npm install -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
# For Vitest:
npm install -D @testing-library/react vitest happy-dom
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "happy-dom",
    setupFiles: ["./tests/setup.ts"],
  },
});
// tests/setup.ts — グローバルなテストのセットアップ
import "@testing-library/jest-dom/vitest";

クエリの優先順位

Testing Library にはクエリの優先順位があります。動作する最も高い優先順位のものを使用してください。

// 優先度 1: アクセシブルなロール (最高 — アクセシビリティもテストする)
screen.getByRole("button", { name: "Submit" });
screen.getByRole("textbox", { name: "Email" });
screen.getByRole("heading", { level: 1 });

// 優先度 2: ラベルテキスト (フォーム)
screen.getByLabelText("Email address");

// 優先度 3: プレースホルダーテキスト
screen.getByPlaceholderText("Search...");

// 優先度 4: テキストコンテンツ
screen.getByText("Welcome back!");

// 優先度 5: 表示値
screen.getByDisplayValue("user@example.com");

// 優先度 6: 代替テキスト (画像)
screen.getByAltText("Company logo");

// 最終手段: テスト ID (可能な限り避ける)
screen.getByTestId("complex-widget");

コンポーネントの動作のテスト

// LoginForm.test.tsx — ユーザーの視点からログインフォームをテストする
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";

describe("LoginForm", () => {
  it("メールアドレスとパスワードでフォームを送信する", async () => {
    const onSubmit = vi.fn();
    const user = userEvent.setup();

    render(<LoginForm onSubmit={onSubmit} />);

    // アクセシブルなロール/ラベルで要素を見つける
    await user.type(screen.getByLabelText("Email"), "kai@example.com");
    await user.type(screen.getByLabelText("Password"), "secret123");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(onSubmit).toHaveBeenCalledWith({
      email: "kai@example.com",
      password: "secret123",
    });
  });

  it("無効なメールアドレスの場合、バリデーションエラーを表示する", async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={vi.fn()} />);

    await user.type(screen.getByLabelText("Email"), "not-an-email");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(screen.getByRole("alert")).toHaveTextContent("Invalid email address");
  });

  it("ローディング中は送信を無効にする", async () => {
    render(<LoginForm onSubmit={() => new Promise(() => {})} />);
    const user = userEvent.setup();

    await user.type(screen.getByLabelText("Email"), "kai@example.com");
    await user.type(screen.getByLabelText("Password"), "secret123");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(screen.getByRole("button", { name: /signing in/i })).toBeDisabled();
  });
});

非同期テスト

// UserProfile.test.tsx — 非同期データフェッチのテスト
import { render, screen, waitFor } from "@testing-library/react";
import { UserProfile } from "./UserProfile";

it("ユーザーデータをロードして表示する", async () => {
  render(<UserProfile userId="123" />);

  // ローディング状態
  expect(screen.getByText("Loading...")).toBeInTheDocument();

  // データがロードされるのを待つ
  await waitFor(() => {
    expect(screen.getByRole("heading")).toHaveTextContent("Kai Chen");
  });

  expect(screen.getByText("kai@example.com")).toBeInTheDocument();
});

it("失敗時にエラー状態を表示する", async () => {
  // API の失敗をモックする
  server.use(http.get("/api/users/123", () => HttpResponse.error()));

  render(<UserProfile userId="123" />);

  await waitFor(() => {
    expect(screen.getByRole("alert")).toHaveTextContent("Failed to load");
  });
});

例 1: バリデーション付きの複雑なフォームをテストする

ユーザープロンプト: 「バリデーション付きの複数ステップの登録フォームのテストを記述してください。」

エージェントは、ユーザーの視点から各ステップを入力し、無効な入力でバリデーションエラーが表示されることを検証し、送信が成功することを確認するテストを記述します。

例 2: アクセシブルなデータテーブルをテストする

ユーザープロンプト: 「ソート可能なデータテーブルをテストしてください — ソート、フィルタリング、およびページネーションが動作することを確認してください。」

エージェントは、ロールによってテーブルヘッダーをクエリし、クリックしてソートし、行の順序が変更されることを検証し、フィルター入力に入力し、ページをナビゲートします。

ガイドライン

  • 最初にロールでクエリするgetByRole はアクセシビリティを無料でテストします
  • fireEvent よりも userEvent — 実際のユーザーの動作をシミュレートします (フォーカス、タイプ、ブラー)
  • テスト開始時に userEvent.setup() — テスト用のユーザーインスタンスを作成します
  • 非同期処理には waitFor — データフェッチ後に要素が表示されるのを待ちます
  • getByTestId は避ける — 必要な場合は、コンポーネントにアクセシビリティの問題がある可能性があります
  • screen はグローバル — render 結果をデストラクトする必要はありません
  • jest-dom の toBeInTheDocument() — 読みやすいアサーション
  • 実装をテストしない — 状態の値、エフェクトのトリガー、再レンダリングの回数
  • 構造ではなく動作をテストする — 「ユーザーにエラーが表示される」ではなく「エラー div にクラス active がある」
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Testing Library

Overview

Testing Library tests UI components from the user's perspective — find elements by their accessible role, text content, or label, not by CSS class or test ID. If a user can't find a button, your test shouldn't find it either. This approach catches accessibility issues by default, survives refactors (rename a CSS class and tests still pass), and produces tests that actually prove the UI works.

When to Use

  • Testing React/Vue/Svelte components
  • Want tests that survive refactoring (no CSS selectors or internal state checks)
  • Need to verify accessibility (ARIA roles, labels)
  • Testing user interactions (clicks, typing, form submission)
  • Integration testing of component behavior (not snapshot testing)

Instructions

Setup

npm install -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
# For Vitest:
npm install -D @testing-library/react vitest happy-dom
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "happy-dom",
    setupFiles: ["./tests/setup.ts"],
  },
});
// tests/setup.ts — Global test setup
import "@testing-library/jest-dom/vitest";

Query Priority

Testing Library has a priority order for queries — use the highest priority that works:

// Priority 1: Accessible roles (best — tests accessibility too)
screen.getByRole("button", { name: "Submit" });
screen.getByRole("textbox", { name: "Email" });
screen.getByRole("heading", { level: 1 });

// Priority 2: Label text (forms)
screen.getByLabelText("Email address");

// Priority 3: Placeholder text
screen.getByPlaceholderText("Search...");

// Priority 4: Text content
screen.getByText("Welcome back!");

// Priority 5: Display value
screen.getByDisplayValue("user@example.com");

// Priority 6: Alt text (images)
screen.getByAltText("Company logo");

// Last resort: test IDs (avoid if possible)
screen.getByTestId("complex-widget");

Testing Component Behavior

// LoginForm.test.tsx — Test a login form from the user's perspective
import { render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";

describe("LoginForm", () => {
  it("submits the form with email and password", async () => {
    const onSubmit = vi.fn();
    const user = userEvent.setup();

    render(<LoginForm onSubmit={onSubmit} />);

    // Find elements by their accessible role/label
    await user.type(screen.getByLabelText("Email"), "kai@example.com");
    await user.type(screen.getByLabelText("Password"), "secret123");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(onSubmit).toHaveBeenCalledWith({
      email: "kai@example.com",
      password: "secret123",
    });
  });

  it("shows validation error for invalid email", async () => {
    const user = userEvent.setup();
    render(<LoginForm onSubmit={vi.fn()} />);

    await user.type(screen.getByLabelText("Email"), "not-an-email");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(screen.getByRole("alert")).toHaveTextContent("Invalid email address");
  });

  it("disables submit while loading", async () => {
    render(<LoginForm onSubmit={() => new Promise(() => {})} />);
    const user = userEvent.setup();

    await user.type(screen.getByLabelText("Email"), "kai@example.com");
    await user.type(screen.getByLabelText("Password"), "secret123");
    await user.click(screen.getByRole("button", { name: "Sign in" }));

    expect(screen.getByRole("button", { name: /signing in/i })).toBeDisabled();
  });
});

Async Testing

// UserProfile.test.tsx — Testing async data fetching
import { render, screen, waitFor } from "@testing-library/react";
import { UserProfile } from "./UserProfile";

it("loads and displays user data", async () => {
  render(<UserProfile userId="123" />);

  // Loading state
  expect(screen.getByText("Loading...")).toBeInTheDocument();

  // Wait for data to load
  await waitFor(() => {
    expect(screen.getByRole("heading")).toHaveTextContent("Kai Chen");
  });

  expect(screen.getByText("kai@example.com")).toBeInTheDocument();
});

it("shows error state on failure", async () => {
  // Mock API failure
  server.use(http.get("/api/users/123", () => HttpResponse.error()));

  render(<UserProfile userId="123" />);

  await waitFor(() => {
    expect(screen.getByRole("alert")).toHaveTextContent("Failed to load");
  });
});

Examples

Example 1: Test a complex form with validation

User prompt: "Write tests for a multi-step registration form with validation."

The agent will write tests that fill each step from the user's perspective, verify validation errors appear on invalid input, and confirm successful submission.

Example 2: Test an accessible data table

User prompt: "Test a sortable data table — verify sorting, filtering, and pagination work."

The agent will query table headers by role, click to sort, verify row order changes, type into filter input, and navigate pages.

Guidelines

  • Query by role firstgetByRole tests accessibility for free
  • userEvent over fireEvent — simulates real user behavior (focus, type, blur)
  • userEvent.setup() at test start — creates a user instance for the test
  • waitFor for async — wait for elements to appear after data fetching
  • Avoid getByTestId — if you need it, the component might have accessibility issues
  • screen is global — no need to destructure render result
  • toBeInTheDocument() from jest-dom — readable assertions
  • Don't test implementation — state values, effect triggers, re-render counts
  • Test behavior, not structure — "user sees error" not "error div has class active"