Okulary położone na biurku przed monitorem

Jak pisać testy end-to-end aplikacji React z frameworkiem Cypress

Przyszedł czas na zdobycie wierzchołka testerskiej piramidy. Zabierzemy się za testy e2e (end-to-end). W teorii to najlepszy sposób na upewnienie się, że aplikacja działa jak należy. Możliwość dokładnego odtworzenia zachowań użytkownika w środowisku przeglądarki jest nadzwyczaj obiecująca. Niestety, praktyka szybko obnaża ukryte słabości tego podejścia do testowania. Testy e2e są czasochłonne i niezwykle wrażliwe na zmiany w aplikacji. Czy to oznacza, że lepiej byłoby dać sobie z nimi spokój? Bynajmniej. Możemy mieć z nich nie lada użytek, wystarczy zachować umiar przy tworzeniu przypadków testowych i wyposażyć się w dobre narzędzia. Właśnie o jednym z takich narzędzi będzie mowa w dzisiejszym wpisie. Zobaczymy, jak sprawdza się w akcji zyskujący popularność framework cypress.io.

Disclaimer

W myśl zasady DRY: nie będę się zajmował wyjaśnianiem terminologii i omawianiem procesu planowania przypadków testowych. Szeroko omówiłem tę tematykę w poprzednich wpisach z serii Testowanie komponentów React. Jeżeli nie miałeś okazji ich przeczytać, zachęcam do nadrobienia zaległości.

New kid on the block

Cypress pozwala na testowanie każdej aplikacji uruchamianej w przeglądarce. Jest to rozwiązanie kompletne, zawiera wszystko, co potrzebne do pisania i uruchamiania testów.

Ponadto, mamy do dyspozycji następujące funkcjonalności ułatwiające pracę: snapshoty, hot reloading testów, mechanizm oczekiwania i kilkukrotnego wyszukiwania węzłów DOM (testy kodu asynchronicznego nigdy nie były tak łatwe!). To wszystko składa się w całkiem zgrabną całość.

Zdecydowałem się na wybór tego narzędzia zamiast popularniejszcych technologii bazujących na Selenium (np. Nightwatch.js) ze względu na niski próg wejścia i świetną dokumentację. No dobra, nie ma co ukrywać, nowinkowa chciwość również maczała w tym palce.

Jeżeli to Twoja pierwsza styczność z cypressem, to masz do dyspozycji poradnik w formie tekstowej oraz darmowy kurs video. Z pomocą tych materiałów poznasz sam framework oraz, co ważniejsze, wiele zasad poprawnego pisania testów e2e. Gorąco polecam.

Ja skupię się przede wszystkim na prezentacji podstaw korzystania z cypressa. Po przeczytaniu tego artykułu będziesz gotowy do samodzielnego testowania kluczowych elementów swojego projektu.

Tradycyjnie naszym królikiem doświadczalnym będzie projekt crypto-tracker (demo znajdziesz tutaj).

Zapraszamy cypressa na pokład

Bez niespodzianek zaczynamy od instalacji pakietu:

A następnie dodajemy do package.json nowy skrypt npm:

W przeciwieństwie do skryptów, z których korzystaliśmy przy pracy z Jest i Enzyme, ten nie spowoduje uruchomienia testów. Jego zadaniem jest utworzenie folderu /cypress zawierającego domyślną strukturę plików oraz uruchomienie graficznego panelu zarządzania frameworkiem.

Struktura plików

Przyjrzyjmy się folderom dodanym do projektu:

  • fixtures to odpowiednik __mocks__ znanego nam z Jest. To miejsce do przechowywania sztucznych danych wykorzystywanych jako atrapy w testach.
  • integration to folder, w którym będziemy przechowywali specyfikacje testów.
  • plugins to miejsce na pliki wtyczek. Ich wykorzystanie jest niezbędne do obsługi TypeScripta czy nowości z ES7+.
  • support zawiera plik index.js, wykonywany przed każdym testem. Przypomina trochę setupTests.js z Jest. Możemy w nim zadeklarować zmienne globalne udostępniane we wszystkich przypadkach testowych.

Centrum dowodzenia

Wszystkie przeprowadzane przez nas testy e2e zaczną i skończą się w graficznym panelu zarządzania.

Panel zarządzania Cypress

Jego obsługa jest dziecinnie prosta. Kliknięcie w nazwę testu powoduje uruchomienie go (suprise no. 1). Mamy również możliwość odpalenia wszystkich testów za pomocą „Run all specs” (suprise no. 2). Zaraz obok znajduje się dropdown, w którym wybieramy testowaną przeglądarkę. Niestety, wybór jest czysto symboliczny. Do dyspozycji mamy jedynie Chrome… To niewątpliwie największa wada tego narzędzia. Wsparcie dla pozostałych przeglądarek jest w drodze, postęp prac można śledzić w tym wątku.

Dla poszukiwaczy niesamowitych wrażeń, w zakładce „Runs” możecie nagrywać swoje testerskie wyczyny.

Proces pisania testów

Smoke test na rozgrzewkę

Sprawdzimy, czy crypto-tracker uruchamia się jak należy pod domyślnym adresem url serwera developerskiego.

W tym celu utworzymy nowy przypadek testowy pod ścieżką cypress/integration/app-init.spec.js.

Dzięki hot reloadingowi, test case od razu pojawi się w naszym panelu zarządzania, więc możemy go odpalić…

Komunikat błędu spowodowany brakiem uruchomienia serwera

Woops, falstart. Na szczęście komunikat błędu nie pozostawia wątpliwości co do natury naszego problemu. Zapomniałem o jednej istotnej sprawie. Przed rozpoczęciem testowania, wypadałoby uruchomić wspomniany serwer, którego URL wskazałem w cy.visit(). Wklepanie yarn start do terminala załatwia sprawę.

Smoke-test zakończony sukcesem

Jak widzisz, test wykonał się w okrojonej wersji Chrome. Po lewej widzisz panel testów, a po prawej podgląd aplikacji.

Właściwe przypadki testowe będą odwzorowaniem testów integracyjnych, które przygotowałem w ramach poprzedniego wpisu: Jak pisać testy integracyjne w React z Jest i Enzyme. crypto-tracker jest prostą aplikacją, co nie pozostawiło mi wiele pola do manewrów ;). W swoich projektach stosuj następującą heurystykę: testuj wyłącznie najistotniejsze scenariusze wykorzystania aplikacji i unikaj sprawdzania szczegółów podatnych na częste zmiany (np. stylowanie elementów).

Liczymy kryptowaluty

Dodajmy do testu pierwszą asercję. Wypadałoby sprawdzić, czy faktycznie udało się pobrać z serwera dane kryptowalut i wyświetlić je na ekranie.

Pierw wywołujemy metodę cy.server(), która pozwali nam obsługę komunikacji klient-serwer.

Kontrolę nad konkretnymi zapytaniami umożliwia nam metoda cy.route(). Za jej pomocą nadałem zapytaniu GET alias fetchCryptos.

Aliasy pozwalają na odwoływanie się do zapytania w kolejnych metodach. Ja skorzystałem z tej możliwości przy wywołaniu cy.wait().

Dzięki temu cypress zaczeka z asercją do momentu otrzymania odpowiedzi z serwera (lub upłynięcia czasu wskazanego w timeout).

Jak widzisz, zrezygnowałem z mockowania na rzecz wykonania prawdziwego zapytania do serwera.

Herezja? To zależy.

Testy e2e będą uruchamiane dużo rzadziej niż testy jednostkowe i integracyjne, w których dbaliśmy o mockowanie wszystkich zależności. Dzięki temu możemy zadać sobie pytanie: czy kosztem niewielkiej inwestycji czasu zyskamy pewność co do poprawnej integracji z serwerem?

W przypadku crypto-trackera, gdzie pobieram dane tylko raz, podczas inicjalizacji aplikacji, zdecydowałem się na zrezygnowanie z mockowania.

Oczywiście w aplikacjach, które wykonują tych zapytań więcej, mocki to nadal „way to go”. Mockowanie w cypress jest proste, szczegóły poznasz tutaj.

Jeżeli nie jesteś pewien, jak powinieneś podejść do tego tematu, skorzystaj z analizy plusów i minusów stubowania dostępnej tutaj.

Więcej na temat tego, jak radzić sobie z AJAXem znajdziesz w sekcji dokumentacji Network requests.

Asercje

Wróćmy do przypadku testowego. Jest jeszcze kilka rzeczy, które mogą Cię zastanawiać. Skąd taki selektor, za pomocą którego odwołałem się do węzłów DOM reprezentujących komponent Coin?

Samo dodanie selektora do kontenera komponentu było niezbędne. Cypress to nie Enzyme, nie był tworzony wyłącznie z myślą o Reactcie. Nie mamy możliwości bezpośredniego wyszukiwania komponentów, musimy radzić sobie z tym, co trafia do DOM.

Zamiast dodawać zbędne klasy CSS, skorzystałem z rozwiązania zaproponowanego w kolejnym jasnym punkcie dokumentacji cypressa czyli liście najlepszych praktyk.

Jak słusznie wskazują twórcy cypressa, klasy CSS mają to do siebie, że są podatne na zmiany. Szkoda, żeby refaktoryzacja czy zmiana metodyki była powodem do dodatkowej pracy przy testach. Lepiej wydzielić sobie oddzielny atrybut, który będzie zadeklarowany na wyłączność testów. Stąd właśnie data-cy=coin.

Jeżeli czytałeś mój wpis o testach jednostkowych, na pewno zauważyłeś, że oprócz selektora sama asercja wygląda trochę inaczej. Cypress wykorzystuje bibliotekę asercji Chai, a my do tej pory korzystaliśmy z matcherów Jest. Porównajmy je ze sobą na tym samym przykładzie:

Wystarczy chwilę przyjrzeć się tym dwóm linijkom, żeby stwierdzić, że różnice są tak naprawdę kosmetyczne.

Jedyne, czego nam trzeba, to zapoznać się z metodą should oraz listą dostępnych matcherów asercji.

Podróże w czasie z snapshotami

Zobaczymy w praktyce jeden z ciekawszych feature’ów cypressa czyli snapshoty. Dzięki nim możemy cofać się w czasie do kluczowych momentów wykonywanego przypadku testowego.

Po kliknięciu w (XHR) GET 200 możemy cofnąć się do momentu, w którym doszło do wywołania zapytania GET. Rzecz niesamowicie przydatna przy debugowaniu.

Nie trzeba wiele

Pozostałe trzy przypadki testowe zajęły się weryfikowaniem integracji paska wyszukiwania i listy Coinów. ich kod znajdziesz tutaj. Każdy z nich zamyka się w 10 linijkach kodu. Jedyne dodatkowe metody, z którymi musiałem się zapoznać to type() i clear().

Świetny dowód na to, że nie trzeba wiele zainwestować, żeby wyciągnąć wartość z testów e2e w Cypress.

Podsumowanie

Testy e2e z cypress okazały się dla mnie pozytywnym zaskoczeniem. Myślałem, że będzie znacznie więcej do ogarnięcia niż w przypadku Jest i Enzyme – wyszło na odwrót.

Jeżeli nie masz jeszcze doświadczenia w testowaniu, możesz śmiało zacząć od szczytu testerskiej piramidy. Cypress jest bardzo wyrozumiały dla początkujących. Ciężko o uczucie przytłoczenia, z którym możesz się zderzyć przy pierwszych kontaktach z Enzyme. Kiedy już nabierzesz rozpędu, łatwiej będzie zabrać się za testy jednostkowe i integracyjne.

To tyle w temacie testowania Reacta z mojej strony. Wielu czytelników głosowało za rozpoczęciem tej serii, dajcie znać w komentarzach, na ile jesteście usatysfakcjonowani z tego, co dla Was przygotowałem.

Co dalej?

Przy wyborze kolejnej tematyki wpisów sugerowałem się wynikami majowej ankiety.

Wyniki ankiety odnośnie tematyki wpisów. 1 miejsce: Testy, 2 miejsce: Wzorce, 3 miejsce: Redux

Podobnym zainteresowaniem co testowanie cieszyły się wzorce w React. To właśnie o nich będzie kolejna seria na blogu.

Na początek, zgodnie z tradycją, artykuł wprowadzający. Znajdziecie w nim zarys tematu i rozpiskę tematyki kolejnych wpisów. Jeżeli chciałbyś, abym omówił jakiś interesujący Cię wzorzec, śmiało dawaj znać w komentarzu.

Zapowiada się, że kolejna seria będzie naprawdę wartościowa. Dzięki wzorcom przeniesiemy nasz React game na kolejny poziom.

Podobał Ci się dzisiejszy artykuł? Udostępnij go w Twoich ulubionych mediach społecznościowych. Na pewno znajdzie się ktoś, dla kogo również będzie wartościowy.

Bądź na bieżąco. Wystarczy polubić fanpage AlgoSmart na fejsie, obserwować mój profil na Twitterze i regularnie odwiedzać portal Polski Front-end.

Zdjęcie tytułowe autorstwa: unsplash-logoKevin Ku

Marcin Czarkowski

Cześć! Ja nazywam się Marcin Czarkowski, a to jest AlgoSmart - blog, na którym dzielę się wiedzą o ReactJS oraz JavaScript. Tworzę poradniki na YouTube i jestem współtwórcą przeprogramowani.pl

  • Łukasz

    Dzięki za artykuł. Rzeczowo i konkretnie, jak zwykle zresztą… 🙂