Von Snapshots zu Verhalten: Migration zu React Testing Library
In typischen Projekten konzentrieren sich Tests oft auf die Mark-up-Struktur statt auf die tatsächliche Funktionalität. Snapshot-Tests erfassen den DOM-Zustand – aber sie brechen bei jeder Refaktorisierung: Änderungen an CSS-Klassen, Umstellung von Attributen oder Optimierungen beim Rendern. Echte Logikfehler bleiben unentdeckt.
Ein problematisches Testbeispiel mit Enzyme
describe("UserProfileCards", () => {
it("should render", () => {
const wrapper = shallow(
<Provider store={store}>
<UserProfileCards items={mockedUserProfiles} />
</Provider>
)
expect(wrapper).toMatchSnapshot()
})
})
Dieser Test vermittelt keine echte Sicherheit bezüglich des Komponentenverhaltens. Er prüft nur die Struktur – nicht das, was der Nutzer tatsächlich erlebt.
Implementierungskoppelung: Falsch-positive Ergebnisse
Tests, die an interne Details gebunden sind, verursachen ein doppeltes Problem: Sie scheitern bei Refaktorisierungen und übersehen echte Fehler.
Betrachten wir eine CollapseIcon-Komponente mit einem opened-Status:
import CollapseIcon from "./CollapseIcon"
import { shallow } from "enzyme"
describe("CollapseIcon", () => {
it("should render", () => {
const wrapper = shallow(<CollapseIcon opened={false} />)
expect(wrapper).toMatchSnapshot()
})
it("should react to closed", () => {
const wrapper = shallow(<CollapseIcon opened={false} />)
expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_closed")).toEqual(true)
})
it("should react to opened", () => {
const wrapper = shallow(<CollapseIcon opened={true} />)
expect(wrapper.find(".collapse__icon").hasClass("collapseIcon_opened")).toEqual(true)
})
})
Probleme:
- Snapshots erkennen keine Fehler in Animationslogik.
- Klassenprüfungen brechen bei Stilrefaktorierungen.
- Tests laufen weiter, auch wenn Animationen defekt sind.
Ein weiteres Beispiel: enge Kopplung an Lokalisierungs-Konstanten:
it("should render no-answers button", () => {
const button = getButton(mockCommentsList[0])
const noAnswersText = `${mockedTranslation.t(`${tNamespace}no-answers`)}
expect(button.text()).toBe(noAnswersText)
})
Ändert man den Schlüssel in tNamespace, bricht der Test – selbst wenn der Text korrekt angezeigt wird. Lösung: Verschiebe Konstanten in eine gemeinsame Module, die sowohl von Komponente als auch Tests importiert werden.
Unklare Testbeschreibungen verschärfen das Problem:
- "should show tooltip" – keine Bedingungen.
- "should render without tooltipLabel" – kein erwartetes Ergebnis.
React Testing Library: Testen von Nutzerverhalten
RTL verändert die Grundannahme: Tests simulieren echte Nutzeraktionen und ignorieren interne Details. Kein Zugriff auf Zustand, Props oder Methoden – nur auf die sichtbare Oberfläche.
Vorteile von RTL:
- Nutzerzentrierte Ausrichtung: Klicks, Eingaben, Bildschirmlesegeräte.
- Refaktorisierungssicher: Unabhängig von Klassen, IDs oder internem Zustand.
- API:
render(),screen.getByRole(),fireEvent.click(),waitFor(). - Asynchrone Unterstützung:
findByRole()für Datenladevorgänge. - Barrierefreiheit:
getByLabelText()stellt sicher, dass Labels korrekt sind.
RTL ist für Robustheit gebaut: Tests spiegeln echte Nutzung wider, nicht die DOM-Struktur.
Checkliste für qualitativ hochwertige Tests
Tests schreiben
- Zweck klären: Definiere genau, was getestet wird (z. B. "zeigt Tooltip an, wenn isTooltipShow=true").
- Nutzerperspektive: Aktionen wie ein echter Nutzer –
getByRole('button'),fireEvent. - Isolation: Mocks für APIs; keine Abhängigkeiten zwischen Tests.
- Beschreibungen: Vollständige Sätze, die ohne Code lesbar sind.
- Abdeckung: Hauptpfad + Grenzfälle + Fehlerzustände.
Tests überprüfen
- Ist das Ziel klar?
- Werden Implementierungsdetails (Klassen, Zustand) vermieden?
- Ist der Test isoliert?
- Sind Beschreibungen informativ?
- Sind Kommentare konstruktiv?
Team-Migration organisieren
Veranstalte einen Migration-Workshop: Sammle problematische Tests, analysiere Beispiele, wechsle zu RTL. Empfehlungen:
- Teile Sessions in 45-Minuten-Abschnitte mit praktischer Übung.
- Refaktorisiere gemeinsam per Pair Programming.
- Praxis vor Theorie priorisieren.
Legacy-Migration: Schreibe neue Tests in RTL, ersetze alte schrittweise. Fallbeispiel Airbnb: 3.500 Dateien in 6 Wochen umgeschrieben mit einer LLM-gesteuerten Pipeline (75 % automatisiert).
Wichtige Erkenntnisse
- Denkshift: von Markup zu Nutzerverhalten.
- RTL vs Enzyme: RTL blockiert Zugriff auf Interna; Enzyme fördert ihn.
- Checklisten: Pflicht für Schreiben und Überprüfen.
- Workshops: Dialog schlägt Monolog.
- Migration: Automatisierung beschleunigt Fortschritte, aber Verständnis der Philosophie ist essenziell.
— Editorial Team
Noch keine Kommentare.