Was ist Testing Library?
Testing Library ist eine Familie von Bibliotheken, die dir dabei helfen, UI-Komponenten so zu testen, wie ein Nutzer sie erlebt. Anstatt auf die internen Details einer Komponente zuzugreifen, renderst du sie, findest Elemente so, wie es ein Mensch tun würde – anhand ihrer Rolle, ihres Labels oder des sichtbaren Textes – und interagierst mit ihnen über realistische Events.
Die Kernidee ist eine Reaktion auf das Testen von Implementierungsdetails. Ältere Ansätze renderten eine Komponente und prüften dann den internen State, Props oder eine spezifische DOM-Struktur. Solche Tests brachen bei jedem Refactoring zusammen, während sie nur sehr wenige echte Bugs fanden. Testing Library dreht die Perspektive um: Wenn der Nutzer es sehen und benutzen kann, kannst du es testen – und der Test überlebt Änderungen an der internen Funktionsweise der Komponente.
Rendering und Querying
Du renderst eine Komponente und fragst anschließend das resultierende DOM über screen ab.
// LoginForm.test.jsx
import { render, screen } from "@testing-library/react";
import { LoginForm } from "./LoginForm";
test("shows the email field", () => {
render(<LoginForm />);
expect(screen.getByLabelText("Email")).toBeInTheDocument();
expect(screen.getByRole("button", { name: "Sign in" })).toBeInTheDocument();
});
screen ist die empfohlene Query-Oberfläche. Sie durchsucht das gesamte gerenderte Dokument, was eher widerspiegelt, wie ein Nutzer die Seite wahrnimmt, anstatt nur einen bestimmten Container zu betrachten.
Die richtige Query auswählen
Testing Library bietet viele Queries an, wobei die Reihenfolge entscheidend ist. Bevorzuge diejenigen, die am nächsten an der Perspektive des Nutzers liegen:
- getByRole — Buttons, Links, Überschriften, Inputs und mehr, gematcht über den accessible name. Dies ist der beste Standard.
- getByLabelText — Formularsteuerungen, die über ihr Label gefunden werden.
- getByPlaceholderText — wenn ein Placeholder das einzige verfügbare Label ist.
- getByText — nicht-interaktive Textinhalte.
- getByDisplayValue — der aktuelle Wert eines Formularelements.
- getByAltText — Bilder über ihren Alt-Text.
- getByTitle und getByTestId — als letzte Auswege.
// queries.jsx
screen.getByRole("heading", { level: 1, name: "Dashboard" });
screen.getByLabelText("Password");
screen.getByText("Welcome back");
screen.getByAltText("Company logo");
screen.getByTestId("chart");
getByRole ist besonders leistungsfähig, da es gleichzeitig als Accessibility-Check dient. Wenn ein Element keinen accessible name hat, schlägt die Query fehl – was bedeutet, dass dein Markup wahrscheinlich ebenfalls nicht barrierefrei ist.
Jede Query hat verschiedene Varianten: getBy wirft einen Fehler, wenn nichts matcht, queryBy gibt null zurück und findBy gibt ein Promise zurück, das wartet. Nutze queryBy, um zu prüfen, ob etwas nicht vorhanden ist, und findBy nach einem asynchronen Update.
Interaktion mit userEvent
userEvent simuliert die vollständige Ereignissequenz, die ein echter Benutzer auslöst.
// Search.test.jsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Search } from "./Search";
test("submits the query", async () => {
const onSearch = vi.fn();
render(<Search onSearch={onSearch} />);
await userEvent.type(screen.getByLabelText("Search"), "vitest");
await userEvent.click(screen.getByRole("button", { name: "Go" }));
expect(onSearch).toHaveBeenCalledWith("vitest");
});
userEvent verarbeitet Fokus-, Tastatur- und Pointer-Events gemeinsam, wodurch Bugs gefunden werden, die ein einzelnes fireEvent.change übersehen würde. Bevorzuge es für alles, außer in den seltenen Fällen, in denen es nicht ausreicht.
Asynchrones Verhalten
Die meisten Komponenten aktualisieren sich asynchron, sei es durch einen fetch, einen Timer oder eine Transition. Verwenden Sie findBy und waitFor anstelle von willkürlichen Verzögerungen.
// Users.test.jsx
test("renders users after loading", async () => {
render(<Users />);
expect(screen.getByText("Loading…")).toBeInTheDocument();
const items = await screen.findAllByRole("listitem");
expect(items).toHaveLength(3);
});
findBy wartet auf ein passendes Element und schlägt mit einer hilfreichen Fehlermeldung fehl, falls dieses nie erscheint. waitFor wird verwendet, um auf eine Assertion oder eine Bedingung zu warten, die keine einfache Elementsuche ist. Verwenden Sie niemals echte Timeouts; diese machen Tests langsam und instabil.
Assertions mit jest-dom
Das begleitende @testing-library/jest-dom Paket fügt Matcher hinzu, die den DOM-Zustand auf natürliche Weise beschreiben.
// matchers.test.jsx
expect(button).toBeDisabled();
expect(input).toHaveValue("[email protected]");
expect(dialog).not.toBeInTheDocument();
expect(heading).toHaveTextContent("Dashboard");
expect(link).toHaveAttribute("href", "/docs");
Diese Matcher machen die Absicht deutlicher als reine Prüfungen von DOM-Properties und liefern bessere Fehlermeldungen.
Accessibility-first Testing
Da getByRole auf zugängliche Rollen und Namen setzt, werden Barrierefreiheit-Probleme durch diese Art des Testens frühzeitig aufgedeckt. Wenn Sie einen Button nicht über seine Rolle finden können, können das Screenreader-Nutzer ebenfalls nicht. Das Hinzufügen von getByRole und toHaveAccessibleName zu Ihren Tests ist einer der effizientesten Wege, um die Barrierefreiheit zu verbessern, und ersetzt oft die Notwendigkeit eines separaten automatisierten Audits.
Testing Library über Frameworks hinweg
Die Library bietet Adapter für React, Vue, Svelte, Angular und weitere. Die Query-API ist identisch, sodass das mentale Modell übertragbar bleibt, selbst wenn sich das Setup unterscheidet. Der React-Adapter ist am weitesten verbreitet und wird hier beispielhaft gezeigt.
Best Practices
- Suchen Sie zuerst nach der Rolle, dann nach dem Label und schließlich nach dem Text; verwenden Sie Test-IDs nur als letzten Ausweg.
- Verwenden Sie
userEventanstelle vonfireEvent. - Nutzen Sie
awaitfürfindByundwaitFor, anstatt feste Verzögerungen (Delays) einzubauen. - Prüfen Sie das, was der Benutzer sieht, und nicht den internen Zustand.
- Halten Sie Tests fokussiert auf jeweils ein einziges Verhalten.
- Bereinigen Sie die Umgebung automatisch zwischen den Tests; Testing Library erledigt dies standardmäßig.
- Betrachten Sie ein fehlgeschlagenes
getByRoleals Hinweis auf ein Accessibility-Problem.
Häufige Fehler
- Die Nutzung von
container.querySelectorund das Testen von Klassennamen. - Die Verwendung von
fireEvent, wennuserEventmehr abdecken würde. - Willkürliche
setTimeout-Wartezeiten anstelle vonfindBy. - Snapshots des gesamten gerenderten Trees.
- Assertions auf dem Component-State oder den Props anstatt auf dem DOM.
- Test-IDs an jedem Element hinzufügen und dadurch die Vorteile der Accessibility verlieren.
Wie geht es weiter?
Testing Library ist die Komponentenebene einer soliden Teststrategie. Nutzen Sie sie zusammen mit Vitest oder Jest, vertiefen Sie Ihr React-Wissen, um Komponenten testbar zu gestalten, und decken Sie vollständige User Journeys mit Playwright ab. Nehmen Sie sich anschließend eine bereits bestehende Komponente vor und schreiben Sie einen Test aus der Perspektive des Nutzers.