Powrót do strony głównej

React Testing Library: rezygnacja ze snapshotów

Artykuł opisuje przejście od Enzyme do React Testing Library w projekcie. Analiza problemów ze snapshotami i testami implementacji. Checklisty do pisania i przeglądu testów, rekomendacje dotyczące meetupów i migracji.

RTL zamiast Enzyme: testy zachowania React
Advertisement 728x90

Od snapshotów do zachowań: przejście na React Testing Library w testach

W typowych projektach testy często skupiają się na strukturze kodu, a nie na funkcjonalności. Snapshoty zapisują stan DOM, ale łamają się przy każdym refaktoryzacji — zmiana klas CSS, przestawienie atrybutów czy optymalizacja renderowania. Prawdziwe błędy w logice pozostają niewidoczne.

Przykład problematycznego testu w Enzyme:

describe("UserProfileCards", () => {
    it("should render", () => {
        const wrapper = shallow(
            <Provider store={store}>
                <UserProfileCards items={mockedUserProfiles} />
            </Provider>
        )
        expect(wrapper).toMatchSnapshot()
    })
})

Taki test nie daje pewności, że komponent działa poprawnie. Sprawdza strukturę, a nie efekt dla użytkownika.

Google AdInline article slot

Przyczepianie się do implementacji: fałszywe sygnały

Testy zależne od szczegółów wewnętrznych tworzą dwuznaczną sytuację: zawalają się przy refaktoryzacji i pomijają błędy.

Rozważmy komponent CollapseIcon z właściwością opened:

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)
    })
})

Problemy:

Google AdInline article slot
  • Snapshoty nie wykrywają błędów w animacji.
  • Sprawdzanie klas psuje się przy zmianie stylów.
  • Testy przechodzą nawet, gdy animacja jest uszkodzona.

Inny przykład — sztywna zależność od stałych lokalizacyjnych:

it("should render no-answers button", () => {
    const button = getButton(mockCommentsList[0])
    const noAnswersText = `${mockedTranslation.t(`${tNamespace}no-answers`)}`
    expect(button.text()).toBe(noAnswersText)
})

Zmiana klucza w tNamespace złamie test, mimo że tekst przycisku jest poprawny. Rozwiązanie: przenieść stałe do wspólnego modułu, importowanego zarówno przez komponenty, jak i testy.

Niejasne opisy testów pogarszają sytuację:

Google AdInline article slot
  • "should show tooltip" — bez warunków.
  • "should render without tooltipLabel" — bez oczekiwanego wyniku.

React Testing Library: testowanie zachowań

RTL zmienia paradigma: testy symulują działania użytkownika, ignorując szczegóły wewnętrzne. Brak dostępu do state, props czy metod — tylko do widocznego interfejsu.

Zalety RTL:

  • Skupienie na użytkowniku: kliknięcia, wpisywanie tekstu, czytanie przez screen.
  • Bezpieczne przy refaktoryzacji: nie zależy od klas, id ani wewnętrznego stanu.
  • API: render(), screen.getByRole(), fireEvent.click(), waitFor().
  • Asynchroniczność: findByRole() do ładowania danych.
  • Dostępność (a11y): getByLabelText() sprawdza dostępność.

RTL został zaprojektowany pod kątem odporności: testy odzwierciedlają rzeczywiste użycie, a nie strukturę DOM.

Listy kontrolne dla dobrych testów

Pisanie testów

  • Cel: precyzyjnie określić, co się sprawdza ("wyświetla tooltip przy isTooltipShow=true").
  • Punkt widzenia użytkownika: działania jak użytkownik — getByRole('button'), fireEvent.
  • Izolacja: mocki dla API, brak zależności od innych testów.
  • Opisy: pełne zdania, czytelne bez kodu.
  • Pokrycie: ścieżka główną + przypadki graniczne + błędy.

Rewizja testów

  • Czy cel jest jasny?
  • Czy nie ma szczegółów implementacji (klasy, stan)?
  • Czy test jest izolowany?
  • Czy opisy są informacyjne?
  • Czy komentarze są konstruktywne?

Organizacja migracji w zespole

Spotkanie migracyjne: zebranie problematycznych testów, analiza na przykładach, przejście na RTL. Zalecenia:

  • Sesje po 45 minut z autoryzacją.
  • Wspólna refaktoryzacja testów w trybie pair-programming.
  • Mniej teorii, więcej praktyki na kodzie.

Migracja legacy: nowe testy w RTL, stare stopniowo przepisywane. Przykład Airbnb — 3500 plików w 6 tygodni z pipelinem z LLM (75% automatycznie).

Co ważne

  • Zmiana myślenia: od struktury do zachowań użytkownika.
  • RTL vs Enzyme: RTL blokuje dostęp do wewnętrznych elementów, Enzyme go zachęca.
  • Listy kontrolne: obowiązkowe przy pisaniu i rewizji.
  • Spotkania: dialog ważniejszy niż monolog.
  • Migracja: automatyzacja przyspiesza, ale zrozumienie filozofii jest niezbędne.

— Editorial Team

Advertisement 728x90

Czytaj dalej