Środowiska testujące
W tym rozdziale opisujemy czynniki wpływające na środowisko testujące i nasze rekomendacje dla niektórych scenariuszy.
Narzędzia uruchamiające testy (ang. test runners)
Narzędzia uruchamiające testy, jak np. Jest, mocha czy ava, pozwalają tworzyć zestawy testowe przy użyciu samego JavaScriptu, a także uruchamiać je jako część procesu tworzenia oprogramowania. Dodatkowo, testy mogą być uruchamiane w ramach procesu “ciągłej integracji” (ang. Continuous Integration, CI).
- Jest ma wysoką kompatybilność z projektami reactowymi i obsługuje wiele przydatnych funkcjonalności, jak mockowanie modułów czy sztuczne timery. Dobrze współpracuje również z
jsdom
. Jeśli używasz Create React App, domyślnie masz już dostęp do Jesta z odpowiednią konfiguracją. - Biblioteki takie jak mocha świetnie spisują się w środowiskach przeglądarkowych, dzięki czemu mogą okazać się pomocne w przypadku niektórych testów.
- Testy kompleksowe end-to-end, stosowane w przypadku dłuższych ścieżek rozciągających się na wiele stron aplikacji, wymagają innej konfiguracji.
Mockowanie warstwy renderującej
Testy często uruchamiane są w środowiskach niemających dostępu do prawdziwej warstwy renderującej, np. przeglądarki. Zalecamy więc symulowanie zachowań przeglądarki za pomocą jsdom
, niewielkiej implementacji przeglądarki działającej na Node.js.
W większości przypadków jsdom
zachowuje się jak prawdziwa przeglądarka, lecz nie posiada niektórych funkcjonalności, jak np. generowanie układu strony czy nawigacja. Mimo tego paczka z powodzeniem sprawdza się dla większości komponentów pisanych pod przeglądarkę; działa szybciej niż uruchamianie przeglądarki dla każdego testu z osobna. Ponadto, uruchamia ona testy w tym samym procesie, umożliwiając pisanie kodu sprawdzającego wyrenderowane drzewo DOM.
Podobnie jak prawdziwa przeglądarka, jsdom
pozwala na modelowanie interakcji użytkownika; testy mogą wywoływać zdarzenia na węzłach DOM, a następnie obserwować i sprawdzać wyniki tych akcji (przykład).
Przy takiej konfiguracji można śmiało napisać większość testów dla UI: Jest jako narzędzie uruchamiające testy, jsdom służący do renderowania, interakcje użytkownika określone jako sekwencje zdarzeń przeglądarkowych - a to wszystko “spięte” za pomocą funkcji pomocniczej act()
(przykład). Spora część testów samego Reacta jest napisana przy użyciu powyższej kombinacji.
Jeśli piszesz bibliotekę, która testuje głównie zachowania charakterystyczne dla przeglądarki, a w dodatku wymaga natywnych mechanizmów przeglądarki, jak generowanie układu strony, zalecamy skorzystanie z frameworka mocha.
W środowisku, które uniemożliwia symulowanie modelu DOM (np. podczas testowania komponentów napisanych w React Native na Node.js), możesz skorzystać z narzędzi do symulowania zdarzeń do symulowania interakcji z elementami. Alternatywnie możesz skorzystać z funkcji fireEvent
dostarczonej przez @testing-library/react-native
.
Frameworki jak Cypress, puppeteer czy webdriver służą do uruchamiania testów end-to-end.
Mockowanie funkcji
Podczas pisania testów czasami chcemy podmienić części naszego kodu, które nie posiadają odpowiedników w używanym przez nas środowisku (np. sprawdzanie statusu navigator.onLine
w Node.js). Testy mogą również śledzić niektóre funkcje i obserwować, jak pozostałe części kodu wchodzą z nimi w interakcje. Pomocna okazuje się wtedy możliwość wybiórczego zastąpienia niektórych funkcji wersjami odpowiednimi dla testów.
Szczególnie przydatne okazuje się to przy pobieraniu danych. Zazwyczaj lepiej w testach używać “sztucznych” danych, aby uniknąć spowolnień czy niestabilności z powodu odwołań do prawdziwego API (przykład). Dzięki takiemu zabiegowi testy są przewidywalne. Biblioteki typu Jest czy sinon wspierają mockowanie funkcji. W przypadku testów end-to-end, mockowanie sieci może okazać się trudniejsze, choć należy tego unikać i zamiast tego testować korzystając z prawdziwego API.
Mockowanie modułów
Niektóre komponenty mają zależności w modułach, które mogą nie działać w środowisku testowym lub które zwyczajnie nie są istotne z punktu widzenia naszych testów. Warto wtedy zastąpić te moduły czymś odpowiednim dla danego przypadku (przykład).
W Node.js mockowanie modułów jest wspierane np. przez bibliotekę Jest. Można to również osiągnąć z pomocą paczki mock-require
.
Mockowanie timerów
Komponenty mogą korzystać z funkcji opartych na czasie, np. setTimeout
, setInterval
czy Date.now
. W środowisku testowym warto zamieniać tego typu funkcje na ich zastępniki, które pozwalają ręcznie “sterować czasem”. To świetny sposób na znaczne przyspieszenie działania testów. Testy korzystające z timerów nadal będą wykonywać się w odpowiedniej kolejności, ale zdecydowanie szybciej (przykład). Większość frameworków, również Jest, sinon oraz lolex, pozwalają na mockowanie timerów w testach.
Niekiedy jednak możesz chcieć skorzystać z prawdziwych timerów, na przykład, gdy testujesz animację lub interakcję z endpointem, który zależy od czasu (np. ogranicza częstość odpytywania API). Biblioteki zawierające sztuczne timery pozwalają na łatwe włączanie i wyłączanie tego mechanizmu dla każdego zestawu testowego lub pojedynczego testu. Dzięki temu możesz zdecydować, jak poszczególne testy mają być uruchamiane.
Testy end-to-end
Testy end-to-end są efektywne przy testowaniu dłuższych sekwencji interakcji, zwłaszcza jeśli są one krytyczne dla twojego produktu (np. płatność czy rejestracja). W takich przypadkach konieczne jest przetestowanie, jak przeglądarka renderuje całą aplikację, jak pobiera dane z API, korzysta z sesji i ciasteczek lub nawiguje pomiędzy poszczególnymi stronami. Możesz w nich sprawdzać nie tylko stan drzewa DOM, lecz także sterujące nim dane (np. weryfikując, czy dane zostały zapisane w bazie danych).
Do takich scenariuszy możesz skorzystać z frameworka Cypress, Playwright lub biblioteki Puppeteer, które pozwalają nawigować pomiędzy stronami i sprawdzać rezultaty nie tylko w samej przeglądarce, ale potencjalnie również na backendzie.