Communitygithub.com

test-react

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

Qu'est-ce que test-react ?

Cette compétence 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. Elle couvre React Testing Library, Vitest/Jest, MSW (Mock Service Worker) pour le mocking réseau, les assertions d'accessibilité avec axe, et les tests de hooks personnalisés. L'utilisateur apprendra à configurer l'environnement de test, à utiliser les requêtes accessibles (getByRole, getByLabelText), à interagir avec les composants via userEvent, et à gérer les motifs asynchrones. La compétence aide également à décider entre les tests de composants RTL, les tests de composants Playwright/Cypress et les tests E2E, selon que JSDOM suffit ou non. Elle inclut des exemples concrets, des anti-patrons à éviter et un workflow TDD.

Compatible avec✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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

Test React

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

Quand activer

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

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 via des requêtes accessibles (rôle, label) et userEvent
  • Vérifier la sortie visible et les effets secondaires observables (callback appelé, requête envoyée)

Un test ne doit PAS :

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

Choix de bibliothèque

LanceurQuandNote
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écessaireUtiliser quand JSDOM ne dispose pas de la fonctionnalité requise
Cypress Component TestingNavigateur réel, Cypress déjà utiliséAlternative à Playwright CT

Choisissez-en un. N'utilisez pas RTL + Vitest ET Playwright CT dans le même dépôt sans une séparation claire des responsabilités.

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 entrées
screen.getByLabelText("Email");

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

Variantes :

  • getBy* — lève une exception si aucun élément ne correspond
  • queryBy* — retourne null (utiliser pour "vérifier l'absence")
  • findBy* — asynchrone, retourne une Promise (utiliser 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]" });
});
  • await toujours les appels userEvent
  • Appelez userEvent.setup() une fois par test, réutilisez l'objet user retourné
  • userEvent simule une séquence réelle de navigateur ; 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();

// Assertion d'effet secondaire
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.

Mocking réseau avec MSW

Mock Service Worker mime 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 échoue bruyamment — les passages silencieux sont pires que les échecs.

Surcharge par test

test("affiche une erreur en cas de 500", async () => {
  server.use(
    http.get("/api/users/:id", () => new HttpResponse(null, { status: 500 })),
  );
  render(<UserPage id="1" />);
  expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
});

Encapsulation des fournisseurs

Encapsulez les fournisseurs une fois dans un fichier 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";

Ensuite importez 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 () => {
  // Instanciez QueryClient UNE FOIS par test en dehors du wrapper pour qu'il survive aux re-rendus.
  // Le créer dans la fermeture du wrapper réinitialise le 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 uniquement via l'API publique du hook
  • 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. Détecte :

  • Étiquettes manquantes sur les entrées de formulaire
  • Utilisation invalide d'ARIA
  • Mauvais contraste des couleurs (limité — JSDOM n'a pas de moteur CSS réel, donc cela fonctionne uniquement pour les styles en ligne ; le contraste visuel appartient à Playwright)
  • Texte alternatif manquant sur les images
  • Violations de l'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 du rendu :

  • Cassent à chaque modification de style
  • Sont validés mécaniquement pendant la revue
  • Testent un détail d'implémentation (structure DOM), pas le comportement

Utilisations acceptables des snapshots :

  • Fonctions de sérialisation de données pures (formatInvoice(invoice) -> 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 différences visuelles réelles, pas des chaînes DOM.

Quand utiliser Playwright / Cypress

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

  • Rendre une 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 support complet des DevTools

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

Limite de décision :

  • Un hook, un composant de présentation, un formulaire avec logique -> RTL
  • Un composant dont la mise en page importe ou qui utilise des API navigateur absentes de JSDOM -> Playwright CT
  • Un flux utilisateur complet sur 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 principaux + états d'erreur
PagesE2E couvert séparément ; test de base 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 accessibles, 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. Refactorez le composant à la place
  • Moquer les composants enfants par défaut — testez l'intégration, pas l'isolation. Mokez uniquement lorsque l'enfant a des effets secondaires lourds
  • Ignorer les avertissements act() — ils signalent de vrais bugs (mise à jour d'état après démontage, absence d'encapsulation asynchrone)
  • Partager un état mutable entre les tests — instable 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 qui échoue pour la prochaine exigence
VERT   -> Écrire le code minimal du composant pour réussir
REFACTOR -> Améliorer le composant, les tests restent verts
RÉPÉTER -> Prochaine exigence

Pour les nouveaux composants :

  1. Définir le type de props 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 réussir
  5. Ajouter le cas de test suivant
  6. Refactorer lorsque le troisième test similaire révèle un motif

Commandes de test

# Vitest
vitest                            # mode watch
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(/saved successfully/i)).toBeInTheDocument();
});

Test d'une limite d'erreur

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

test("la limite 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 les vraies erreurs ailleurs.
  const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
  try {
    render(
      <ErrorBoundary fallback={<div>Something went wrong</div>}>
        <Broken />
      </ErrorBoundary>,
    );

    expect(screen.getByText("Something went wrong")).toBeInTheDocument();
  } finally {
    errorSpy.mockRestore();
  }
});

Test d'une limite Suspense

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

  expect(screen.getByText("Loading...")).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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

Skills associés