Zurück zur Startseite

React Testing Library: Abschied von Snapshots

Der Artikel beschreibt den Übergang von Enzyme zu React Testing Library im Projekt. Analyse von Snapshot- und Implementierungstest-Problemen. Checklisten zum Schreiben und Überprüfen von Tests, Empfehlungen für Meetups und Migration.

RTL statt Enzyme: React-Verhaltenstests
Advertisement 728x90

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.

Google AdInline article slot

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:

Google AdInline article slot
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.

Google AdInline article slot

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

Advertisement 728x90

Weiterlesen