Communitygithub.com

React-Testing

React-Komponententests mit React Testing Library, Vitest/Jest, MSW für Netzwerk-Mocking, Barrierefreiheits-Assertions mit Axe und die Entscheidungsgrenze zwischen Komponententests und Playwright/Cypress-End-to-End-Tests. Verwenden Sie dies beim Schreiben oder Korrigieren von Tests für React-Komponenten, Hooks oder Seiten.

Was ist React-Testing?

Das **React-Testing**-Skill bietet eine umfassende Anleitung für verhaltensorientierte React-Komponententests, benutzerdefinierte Hook-Tests, Barrierefreiheits-Assertions und Netzwerk-Mocking mit MSW (Mock Service Worker). Entwickler können damit Tests schreiben, die sich darauf konzentrieren, was Benutzer sehen und tun, anstatt auf Implementierungsdetails, und so robuste und wartbare Tests gewährleisten. Es unterstützt die Test-Runner Vitest und Jest, wobei Vitest für moderne Vite-basierte Setups und Jest für etablierte Projekte wie Next.js bevorzugt wird. Für reale Browserfunktionen, die von JSDOM nicht abgedeckt werden, wird auf Playwright Component Testing oder Cypress Component Testing verwiesen. Der Leitfaden enthält praktische Beispiele für die Benutzerinteraktion mit `userEvent`, asynchrone Muster, Netzwerk-Mocking mit MSW, Provider-Wrapping und Barrierefreiheitsprüfungen mit axe. Anti-Patterns wie die Verwendung von `container.querySelector`, das Mocken von React oder das Ignorieren von `act()`-Warnungen werden klar definiert, um häufige Fehler zu vermeiden. Das Skill ist besonders nützlich für Entwickler, die Testabdeckung zu Legacy-Komponenten hinzufügen, von Enzyme migrieren oder ein neues React-Projekt mit einem soliden Test-Framework einrichten.

Funktioniert mit✓Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-testing

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

React Testing

Umfassende React-Testing-Muster für verhaltensorientierte Komponententests, Tests benutzerdefinierter Hooks, Barrierefreiheits-Assertions und Netzwerk-Mocking.

Wann aktivieren

  • Schreiben von Tests für React-Komponenten, benutzerdefinierte Hooks oder Seiten
  • Hinzufügen von Testabdeckung zu ungetesteten Legacy-Komponenten
  • Migration von Enzyme oder Klassenkomponenten-Mustern zu React Testing Library
  • Einrichten von Vitest oder Jest für ein neues React-Projekt
  • Mocken von HTTP-Anfragen in Tests
  • Assertions zu Barrierefreiheitsverstößen
  • Entscheiden, welche Tests zu RTL vs. Playwright Component Testing vs. vollständigem E2E gehören

Kernprinzip

Teste, was der Benutzer sieht und tut, nicht Implementierungsdetails.

Ein Test sollte:

  • Die Komponente mit denselben Providern rendern, die sie in Produktion hat
  • Mit ihr über zugängliche Queries (Rolle, Label) und userEvent interagieren
  • Sichtbare Ausgabe und beobachtbare Seiteneffekte (Callback ausgelöst, Anfrage gesendet) assertieren

Ein Test sollte NICHT:

  • Komponentenstatus, an Kinder übergebene Props oder aufgerufene Hooks inspizieren
  • React selbst oder Framework-Hooks mocken
  • Auf die Anzahl der Renderings oder die DOM-Struktur assertieren, die keinen Einfluss auf Benutzer haben

Bibliothekswahl

RunnerWannHinweis
VitestVite, Remix, moderne SetupsSchneller, nativer ESM, Jest-kompatible API
JestNext.js, CRA, etablierte ReposStandard für viele React-Projekte
Playwright Component TestingEchter Browser-Engine erforderlichVerwenden, wenn JSDOM die benötigte Funktion nicht bietet
Cypress Component TestingEchter Browser, Cypress bereits im EinsatzAlternative zu Playwright CT

Wähle einen aus. Verwende nicht RTL + Vitest UND Playwright CT im selben Repo, es sei denn, du hast eine klare Aufgabentrennung.

Query-Priorität

React Testing Library stellt Queries in drei Stufen bereit – verwende sie von oben nach unten:

  1. Für alle zugänglich: getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValue
  2. Semantisch: getByAltText, getByTitle
  3. Test-IDs (Notausgang): getByTestId
// Am besten
screen.getByRole("button", { name: /save/i });

// OK für Eingaben
screen.getByLabelText("Email");

// Letzter Ausweg
screen.getByTestId("save-btn");

Varianten:

  • getBy* — wirft Fehler, wenn kein Treffer
  • queryBy* — gibt null zurück (für "Abwesenheit assertieren")
  • findBy* — asynchron, gibt ein Promise zurück (für Elemente, die nach asynchroner Arbeit erscheinen)

Benutzerinteraktion mit userEvent

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

test("submits the form", 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 bei userEvent-Aufrufen immer verwenden
  • userEvent.setup() einmal pro Test aufrufen, das zurückgegebene user wiederverwenden
  • userEvent simuliert eine echte Browser-Sequenz; fireEvent sendet ein einzelnes synthetisches Ereignis aus — userEvent bevorzugen

Asynchrone Muster

// Element, das nach asynchroner Arbeit erscheint
expect(await screen.findByText("Loaded")).toBeInTheDocument();

// Seiteneffekt-Assertion
await waitFor(() => expect(saveSpy).toHaveBeenCalled());

// Element, das verschwinden sollte
await waitForElementToBeRemoved(() => screen.queryByText("Loading"));

Niemals setTimeout + Assertion — anfällig. Verwende die obigen Matcher.

Netzwerk-Mocking mit MSW

Mock Service Worker mockt auf Netzwerkebene. Die Komponente, Hooks und die Fetch-Bibliothek verhalten sich exakt wie in Produktion.

Einrichtung

// 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());

Konfiguriere onUnhandledRequest: "error", damit jede nicht gemockte Anfrage den Test lautstark fehlschlagen lässt — stille Erfolge sind schlimmer als rote Tests.

Pro-Test-Überschreibung

test("renders error on 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();
});

Provider-Wrapping

Provider einmal in einer test-utils.tsx wrappen:

// 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";

Dann in jeder Testdatei import { renderWithProviders, screen } from "test-utils" verwenden.

Testen benutzerdefinierter Hooks

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

test("useCounter increments and decrements", () => {
  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 accepts initial value", () => {
  const { result } = renderHook(() => useCounter(10));
  expect(result.current.count).toBe(10);
});

test("useUser fetches user data", async () => {
  // QueryClient ONCE pro Test außerhalb des Wrappers instanziieren, damit er Neu-Renderings überlebt.
  // Wird er innerhalb des Wrapper-Closures erstellt, wird der Cache bei jedem Rendern zurückgesetzt, was zu flaky Tests führt.
  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" });
});
  • Statusändernde Aufrufe in act wrappen
  • Nur über die öffentliche API des Hooks testen
  • Für Hooks, die Kontext verwenden, einen wrapper übergeben

Barrierefreiheits-Assertions

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

test("UserCard has no a11y violations", async () => {
  const { container } = render(<UserCard user={mockUser} />);
  expect(await axe(container)).toHaveNoViolations();
});

Führe axe in Komponententests für jede interaktive Komponente aus. Erkennt:

  • Fehlende Labels auf Formulareingaben
  • Ungültige ARIA-Nutzung
  • Schlechten Farbkontrast (eingeschränkt – JSDOM hat keine echte CSS-Engine, daher funktioniert dies nur für Inline-Styles; visueller Kontrast gehört in Playwright)
  • Fehlende Alt-Texte auf Bildern
  • Verstöße gegen die Überschriftenreihenfolge

Querverweis: skills/accessibility/SKILL.md für das umfassendere A11y-Testspielbuch.

Wann KEINE Snapshot-Tests verwendet werden sollten

Snapshots von gerendertem Output:

  • Brechen bei jeder Stiländerung
  • Werden während der Code-Review durchgewunken
  • Testen Implementierungsdetails (DOM-Struktur), nicht das Verhalten

Akzeptable Snapshot-Verwendungen:

  • Reine Datenserialisierungsfunktionen (formatInvoice(invoice) -> stabiler String)
  • Generierte Konfigurationsdateien (z.B. Webpack-Konfigurations-Output)

Für visuelle Regressionen von Komponenten Playwright/Cyprex-Screenshots oder Percy/Chromatic verwenden – echte visuelle Diffs, keine DOM-Strings.

Wann zu Playwright / Cypress greifen

JSDOM (von Vitest/Jest verwendet) kann nicht:

  • Echtes Layout rendern (Flexbox, Grid, Viewport-Abfragen)
  • Native Browser-Animationen, CSS-Übergänge ausführen
  • Scroll-Verhalten, Drag-and-Drop, Einfügen aus der Zwischenablage testen
  • Iframes, Popups, Downloads, Cross-Origin-Flows handhaben
  • Echtes Netzwerk in einer kontrollierten Umgebung mit vollständiger DevTools-Unterstützung ausführen

Für all dies Playwright Component Testing (Komponententest im echten Browser) oder vollständiges E2E verwenden. Siehe e2e-testing skill.

Entscheidungsgrenze:

  • Ein Hook, eine Präsentationskomponente, ein Formular mit Logik -> RTL
  • Eine Komponente, deren Layout wichtig ist oder die Browser-APIs nutzt, die nicht in JSDOM enthalten sind -> Playwright CT
  • Ein vollständiger Benutzerflow über mehrere Seiten -> Playwright/Cypress E2E

Coverage-Ziele

SchichtZiel
Reine Hilfsfunktionen>=90%
Benutzerdefinierte Hooks>=85%
Präsentationskomponenten>=80% — Verhalten, nicht Zeilen
Container-Komponenten>=70% — goldene Pfade + Fehlerzustände
SeitenE2E separat abgedeckt; Minimum Smoke-Test

Konfiguriere über 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-Patterns

  • container.querySelector("...") — umgeht Barrierefreiheits-Queries, lässt Tests bestehen, wenn echte Benutzer scheitern würden
  • Assertieren der Anzahl von Renderings — Implementierungsdetail
  • jest.mock("react", ...) — niemals React mocken. Die Komponente umgestalten
  • Mocken von Kind-Komponenten standardmäßig — testet die Integration, nicht die Isolation. Nur mocken, wenn das Kind starke Seiteneffekte hat
  • Ignorieren von act()-Warnungen — sie signalisieren echte Fehler (Statusaktualisierung nach Unmount, fehlende asynchrone Wrappung)
  • Gemeinsame Nutzung von mutablem State über Tests hinweg — Flakes, wenn sich die Testreihenfolge ändert
  • Tests, die mit entferntem it.skip() bestehen — dein Test assertiert nicht das, was du denkst

TDD-Workflow

ROT   -> Schreiben eines fehlschlagenden Tests für die nächste Anforderung
GRÜN  -> Schreiben von minimalem Komponenten-Code, der den Test besteht
REFACTOR -> Verbessern der Komponente, Tests bleiben grün
WIEDERHOLEN -> Nächste Anforderung

Für neue Komponenten:

  1. Definiere den Prop-Typ und die Signatur der Komponente
  2. Schreibe den ersten Test für den einfachsten Fall
  3. Stelle sicher, dass er aus dem richtigen Grund fehlschlägt
  4. Implementiere gerade genug, um zu bestehen
  5. Füge den nächsten Testfall hinzu
  6. Refaktoriere, wenn der dritte ähnliche Test ein Muster offenbart

Test-Befehle

# Vitest
vitest                            # watch
vitest run                        # einmalig
vitest run --coverage             # mit Coverage
vitest run path/to/file.test.tsx  # einzelne Datei

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

# CI-Modus
CI=true vitest run --coverage

Verwandt

Beispiele

Formularabsendung mit MSW und userEvent

test("submits user form and shows success", 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();
});

Testen einer Error Boundary

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

test("error boundary renders fallback", () => {
  // Reagiert auf console.error-Rauschen für den erwarteten Fehler, dann wiederherstellen, damit
  // der Spy nicht zwischen Tests leckt und echte Fehler anderswo verbirgt.
  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();
  }
});

Testen einer Suspense Boundary

test("shows loading then content", 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.

Verwandte Skills