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.
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:
- 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ę:
- "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
Brak komentarzy.