Communitygithub.com

React Testing

Tests de composants React avec React Testing Library, Vitest/Jest, MSW pour le mock réseau, assertions d'accessibilité avec axe et la frontière décisionnelle entre les tests de composants et les tests bout en bout Playwright/Cypress.

Qu'est-ce que React Testing ?

Ce skill fournit un guide complet pour les tests de composants React, en se concentrant sur les tests basés sur le comportement plutôt que sur les détails d'implémentation. Il couvre l'utilisation de React Testing Library avec Vitest ou Jest, le mock réseau avec MSW (Mock Service Worker), les assertions d'accessibilité avec axe, et le test de hooks personnalisés. Le skill explique la priorité des requêtes (accessibles, sémantiques, test IDs), l'interaction utilisateur avec `userEvent` qui simule le comportement du navigateur, et les motifs asynchrones. Il détaille également la configuration des fournisseurs de contexte, les tests de hooks avec `renderHook`, et l'utilisation de MSW pour mocker les appels API au niveau réseau. Une section importante est dédiée à la frontière décisionnelle entre les tests de composants RTL et les tests Playwright/Cypress, en précisant quand utiliser chaque outil. Le skill inclut des objectifs de couverture, des anti-patrons à éviter, un workflow TDD, et des exemples concrets comme le test de soumission de formulaire, de barrière d'erreur (Error Boundary) et de Suspense. Il est utile pour les développeurs qui écrivent ou corrigent des tests pour des composants, hooks ou pages React, que ce soit pour un nouveau projet ou pour ajouter des tests à une base de code existante.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/react-testing

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Que fait React Testing ?

Modèles de test React complets pour des tests de composants axés sur le comportement, des tests de hooks personnalisés, des assertions d'accessibilité et le mock au niveau réseau.

Quand activer

  • Écrire des tests pour des composants React, des hooks personnalisés ou des pages
  • Ajouter une couverture de test à des composants hérités non testés
  • Migrer d'Enzyme ou de modèles d'époque de classes vers React Testing Library
  • Configurer Vitest ou Jest pour un nouveau projet React
  • Mocker des requêtes HTTP dans les tests
  • Vérifier les violations d'accessibilité
  • Décider quels tests appartiennent à RTL, Playwright Component Testing ou aux tests E2E complets

Principe Fondamental

Testez ce que l'utilisateur voit et fait, pas les détails d'implémentation.

Un test doit :

  • Rendre le composant avec les mêmes fournisseurs qu'en production
  • Interagir avec lui via des requêtes accessibles (rôle, label) et userEvent
  • Vérifier la sortie visible et les effets de bord observables (callback exécuté, requête envoyée)

Un test NE doit PAS :

  • Inspecter l'état du composant, les props passées aux enfants ou les hooks appelés
  • Mocker React lui-même ou les hooks du framework
  • Vérifier le nombre de rendus ou la structure du DOM au-delà de ce qui affecte les utilisateurs

Choix de la Bibliothèque

ExécuteurQuandRemarque
VitestVite, Remix, configurations modernesPlus rapide, ESM natif, API compatible Jest
JestNext.js, CRA, dépôts établisPar défaut pour de nombreux projets React
Playwright Component TestingMoteur de navigateur réel nécessaireUtilisez lorsque JSDOM manque de la fonctionnalité requise
Cypress Component TestingNavigateur réel, Cypress déjà utiliséAlternative à Playwright CT

Choisissez-en un. N'exécutez pas RTL + Vitest ET Playwright CT dans le même dépôt à moins d'avoir une séparation de voies claire.

Priorité des Requêtes

React Testing Library expose les requêtes en trois niveaux – utilisez du haut vers le bas :

  1. Accessible à tous : getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue
  2. Sémantique : getByAltText, getByTitle
  3. IDs de test (échappatoire) : getByTestId
// Meilleur
screen.getByRole("button", { name: /save/i });

// OK pour les champs de saisie
screen.getByLabelText("Email");

// Dernier recours
screen.getByTestId("save-btn");

Variantes :

  • getBy* — lève une exception si aucune correspondance
  • queryBy* — retourne null (utilisez pour "vérifier l'absence")
  • findBy* — asynchrone, retourne une Promesse (utilisez pour les éléments qui apparaissent après un travail asynchrone)

Interaction Utilisateur avec userEvent

import userEvent from "@testing-library/user-event";

test("soumet le formulaire", async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();
  render(<UserForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText("Email"), "[email protected]");
  await user.click(screen.getByRole("button", { name: /save/i }));

  expect(onSubmit).toHaveBeenCalledWith({ email: "[email protected]" });
});
  • Utilisez toujours await sur les appels userEvent
  • Appelez userEvent.setup() une fois par test, réutilisez l'objet user retourné
  • userEvent simule une séquence de navigateur réelle ; fireEvent déclenche un seul événement synthétique — préférez userEvent

Motifs Asynchrones

// Élément qui apparaît après un travail asynchrone
expect(await screen.findByText("Loaded")).toBeInTheDocument();

// Vérification d'effet de bord
await waitFor(() => expect(saveSpy).toHaveBeenCalled());

// Élément qui devrait disparaître
await waitForElementToBeRemoved(() => screen.queryByText("Loading"));

N'utilisez jamais setTimeout + assertion — instable. Utilisez les matchers ci-dessus.

Mock Réseau avec MSW

Mock Service Worker mocke au niveau réseau. Le composant, les hooks et la bibliothèque fetch se comportent exactement comme en production.

Configuration

// test/setup.ts
import { setupServer } from "msw/node";
import { http, HttpResponse } from "msw";

export const handlers = [
  http.get("/api/users/:id", ({ params }) =>
    HttpResponse.json({ id: params.id, name: "Alice" }),
  ),
  http.post("/api/users", async ({ request }) => {
    const body = await request.json();
    return HttpResponse.json({ id: "new-id", ...body }, { status: 201 });
  }),
];

export const server = setupServer(...handlers);

beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());

Configurez onUnhandledRequest: "error" pour que toute requête non mockée fasse échouer le test de manière bruyante — les passages silencieux sont pires que les échecs.

Surcharge par test

test("affiche l'erreur sur 500", async () => {
  server.use(
    http.get("/api/users/:id", () => new HttpResponse(null, { status: 500 })),
  );
  render(<UserPage id="1" />);
  expect(await screen.findByText(/une erreur s'est produite/i)).toBeInTheDocument();
});

Encapsulation des Fournisseurs

Encapsulez les fournisseurs une fois dans un test-utils.tsx :

// test-utils.tsx
import { render, RenderOptions } from "@testing-library/react";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";

export function renderWithProviders(
  ui: React.ReactElement,
  options?: RenderOptions,
) {
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });

  return render(
    <QueryClientProvider client={queryClient}>
      <ThemeProvider theme={lightTheme}>
        <MemoryRouter>{ui}</MemoryRouter>
      </ThemeProvider>
    </QueryClientProvider>,
    options,
  );
}

export * from "@testing-library/react";

Importez ensuite import { renderWithProviders, screen } from "test-utils" dans chaque fichier de test.

Test de Hooks Personnalisés

import { renderHook, act } from "@testing-library/react";

test("useCounter incrémente et décrémente", () => {
  const { result } = renderHook(() => useCounter(0));

  expect(result.current.count).toBe(0);

  act(() => result.current.increment());
  expect(result.current.count).toBe(1);

  act(() => result.current.decrement());
  expect(result.current.count).toBe(0);
});

test("useCounter accepte une valeur initiale", () => {
  const { result } = renderHook(() => useCounter(10));
  expect(result.current.count).toBe(10);
});

test("useUser récupère les données utilisateur", async () => {
  // Instancier QueryClient UNE FOIS par test en dehors du wrapper pour qu'il survive aux ré-rendus.
  // Le créer à l'intérieur de la fermeture du wrapper réinitialise l'état du cache à chaque rendu, produisant des tests instables.
  const queryClient = new QueryClient({
    defaultOptions: { queries: { retry: false } },
  });
  const wrapper = ({ children }: { children: React.ReactNode }) => (
    <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
  );

  const { result } = renderHook(() => useUser("1"), { wrapper });

  await waitFor(() => expect(result.current.isSuccess).toBe(true));
  expect(result.current.data).toEqual({ id: "1", name: "Alice" });
});
  • Encapsulez les appels modifiant l'état dans act
  • Testez via l'API publique du hook uniquement
  • Pour les hooks qui utilisent le contexte, passez un wrapper

Assertions d'Accessibilité

import { axe, toHaveNoViolations } from "jest-axe"; // ou vitest-axe
expect.extend(toHaveNoViolations);

test("UserCard n'a pas de violations d'accessibilité", async () => {
  const { container } = render(<UserCard user={mockUser} />);
  expect(await axe(container)).toHaveNoViolations();
});

Exécutez axe dans les tests de composants pour chaque composant interactif. Il détecte :

  • Étiquettes manquantes sur les champs de formulaire
  • Utilisation invalide d'ARIA
  • Mauvais contraste de couleur (limité — JSDOM n'a pas de moteur CSS réel, donc cela fonctionne uniquement pour les styles en ligne ; le contraste visuel relève de Playwright)
  • Texte alternatif manquant sur les images
  • Violations d'ordre des titres

Lien croisé : skills/accessibility/SKILL.md pour le guide de test d'accessibilité plus large.

Quand NE PAS Utiliser les Tests de Snapshot

Les snapshots de sortie rendue :

  • Cassent à chaque changement de style
  • Sont validés sans réelle vérification pendant la revue
  • Testent le détail d'implémentation (structure du DOM), pas le comportement

Utilisations acceptables des snapshots :

  • Fonctions de sérialisation de données pures (formatInvoice(facture) -> chaîne stable)
  • Fichiers de configuration générés (par exemple, sortie de configuration webpack)

Pour la régression visuelle sur les composants, utilisez les captures d'écran Playwright/Cypress ou Percy/Chromatic — des diffs visuels réels, pas des chaînes DOM.

Quand Utiliser Playwright / Cypress

JSDOM (utilisé par Vitest/Jest) ne peut pas :

  • Rendre la mise en page réelle (flexbox, grille, requêtes de viewport)
  • Exécuter des animations natives du navigateur, des transitions CSS
  • Tester le comportement de défilement, le glisser-déposer, le collage depuis le presse-papier
  • Gérer les iframes, popups, téléchargements, flux cross-origin
  • Exécuter un réseau réel dans un environnement contrôlé avec le support complet des DevTools

Pour tout cela, utilisez Playwright Component Testing (test de composant dans un navigateur réel) ou un test E2E complet. Voir e2e-testing skill.

Frontière décisionnelle :

  • Un hook, un composant de présentation, un formulaire avec logique -> RTL
  • Un composant dont la mise en page est importante ou qui utilise des API navigateur non présentes dans JSDOM -> Playwright CT
  • Un flux utilisateur complet à travers plusieurs pages -> Playwright/Cypress E2E

Objectifs de Couverture

CoucheObjectif
Utilitaires purs>=90%
Hooks personnalisés>=85%
Composants de présentation>=80% — comportement, pas lignes
Composants conteneurs>=70% — chemins dorés + états d'erreur
PagesE2E couvert séparément ; test de fumée minimum

Configurez via vitest.config.ts / jest.config.js :

// vitest.config.ts
test: {
  coverage: {
    provider: "v8",
    reporter: ["text", "html", "lcov"],
    thresholds: {
      lines: 80,
      functions: 80,
      branches: 70,
      statements: 80,
    },
  },
}

Anti-Patrons

  • container.querySelector("...") — contourne les requêtes d'accessibilité, laisse les tests passer alors que les vrais utilisateurs échoueraient
  • Vérifier le nombre de rendus — détail d'implémentation
  • jest.mock("react", ...) — ne mockez jamais React. Refactorisez le composant à la place
  • Mocker les composants enfants par défaut — teste l'intégration, pas l'isolation. Mockez uniquement lorsque l'enfant a des effets de bord lourds
  • Ignorer les avertissements act() — ils signalent de vrais bugs (mise à jour d'état après démontage, encapsulation asynchrone manquante)
  • Partager un état mutable entre les tests — crée des tests instables lorsque l'ordre des tests change
  • Tests qui passent avec it.skip() supprimé — votre test ne vérifie pas réellement ce que vous pensez

Workflow TDD

ROUGE  -> Écrire un test échouant pour la prochaine exigence
VERT   -> Écrire le code minimal du composant pour passer
REFACTORISATION -> Améliorer le composant, les tests restent verts
RÉPÉTER -> Prochaine exigence

Pour les nouveaux composants :

  1. Définir le type de prop et la signature du composant
  2. Écrire le premier test pour le cas le plus simple
  3. Vérifier qu'il échoue pour la bonne raison
  4. Implémenter juste assez pour passer
  5. Ajouter le cas de test suivant
  6. Refactoriser lorsque le troisième test similaire révèle un motif

Commandes de Test

# Vitest
vitest                            # surveillance
vitest run                        # exécution unique
vitest run --coverage             # avec couverture
vitest run path/to/file.test.tsx  # fichier unique

# Jest
jest --watch
jest --coverage
jest path/to/file.test.tsx

# Mode CI
CI=true vitest run --coverage

Liens connexes

Exemples

Soumission de formulaire avec MSW et userEvent

test("soumet le formulaire utilisateur et affiche le succès", async () => {
  server.use(
    http.post("/api/users", () =>
      HttpResponse.json({ id: "1", name: "Alice" }, { status: 201 }),
    ),
  );

  const user = userEvent.setup();
  renderWithProviders(<UserForm />);

  await user.type(screen.getByLabelText("Name"), "Alice");
  await user.type(screen.getByLabelText("Email"), "[email protected]");
  await user.click(screen.getByRole("button", { name: /save/i }));

  expect(await screen.findByText(/sauvegardé avec succès/i)).toBeInTheDocument();
});

Test d'une barrière d'erreur (Error Boundary)

function Broken() {
  throw new Error("boum");
}

test("la barrière d'erreur affiche le contenu de repli", () => {
  // Supprimer le bruit console.error de React pour l'erreur attendue, puis restaurer pour
  // que l'espion ne fuite pas entre les tests et ne cache pas de vraies erreurs ailleurs.
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
  try {
    render(
      <ErrorBoundary fallback={<div>Une erreur s'est produite</div>}>
        <Broken />
      </ErrorBoundary>,
    );

    expect(screen.getByText("Une erreur s'est produite")).toBeInTheDocument();
  } finally {
    errorSpy.mockRestore();
  }
});

Test d'une barrière Suspense

test("affiche le chargement puis le contenu", async () => {
  renderWithProviders(
    <Suspense fallback={<div>Chargement...</div>}>
      <UserDetail id="1" />
    </Suspense>,
  );

  expect(screen.getByText("Chargement...")).toBeInTheDocument();
  expect(await screen.findByText("Alice")).toBeInTheDocument();
});

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills associés