Piątka ludzi dająca sobie żółwika nad biurkiem z sprzętem elektronicznym

Jak pisać testy integracyjne komponentów React z Jest i Enzyme

Podczas testowania interfejsu użytkownika ograniczanie się sprawdzania odizolowanych od siebie komponentów nie zdaje egzaminu. Funkcjonalności dostarczane przez aplikację zawsze są wypadkową właściwej współpracy kilku jednostek w środowisku przeglądarki. Dopiero kiedy upewnisz się, że podsystemy są właściwie ze sobą zintegrowane, znacznie wzrośnie prawdopodobieństwo, że aplikacja spełni swoje zadanie w akcji. Dzisiaj przeprowadzę Cię przez podstawowy proces planowania i implementacji testów integracyjnych komponentów React w środowisku Jest i Enzyme.

Swoją drogą, istnieje liczna grupa programistów, z Kentem C. Doddsem na czele, która twierdzi, że pisanie dużej liczby testów jednostkowych jest mniej efektywne niż kilka solidnych testów integracyjnych. Efektem takiej filozofii jest zastąpienie klasycznej piramidy testowania takim oto osobliwym kształtem:
Model testowania z naciskiem na testy integracyjne autorstwa Kenta C. Doddsa

Czy taki model faktycznie lepiej sprawdzi się w każdym projekcie? Najzwyczajniej brak mi doświadczenia, żeby udzielić zdecydowanej odpowiedzi. Argumenty Kenta przemawiają do mnie na tyle, że na pewno sprawdzę skuteczność jego podejścia w praktyce.

Niezależnie od całej dyskusji, warto sprawnie radzić sobie zarówno z pisaniem testów jednostkowych, jak i integracyjnych. Ten wpis jest bezpośrednią kontynuacją poprzedniego: Jak pisać testy jednostkowe komponentów w React z Jest i Enzyme. Będę nawiązywał do pojęć, które wyjaśniłem ostatnio, więc jeżeli masz zaległości, zachęcam do ich nadrobienia :).

Jak zorganizować dobrą integrację?

Tak jak mieliśmy do czynienia z kontraktem pojedynczego komponentu, tak możemy wyznaczyć kontrakt podsystemu, który poddamy testom integracyjnym.

Jak wyznaczyć granice podsystemu? Na ogół jego korzeniem jest komponent kontenerowy. W przypadku tak małych projektów jak crypto-tracker, mamy tak naprawdę jeden podsystem, który składa się na całą aplikację. Naszym korzeniem jest komponent App (kod źródłowy znajdziesz tutaj). Dzięki przeprowadzonym wcześniej testom jednostkowym wiemy, że wypełnia swoje zadania w izolacji. Teraz pozostało sprawdzić, jak radzi sobie w zarządzaniu współpracą renderowanych przez niego dzieci: SearchBar i CoinList.

Większość informacji o tym, jak przebiega wspomniana kooperacja, dostarczy nam analiza kodu źródłowego oraz testów jednostkowych.

Umożliwianie współpracy pomiędzy rodzicem a jego dzieckiem to główna odpowiedzialność props, więc właśnie ten obiekt dostarczy nam dużo użytecznych informacji o tym co wymaga przetestowania. W testach jednostkowych mamy informacje co do tego, jakie propsy komponent otrzymuje od swoich przodków oraz co przekazuje swoim dzieciom. Jeżeli tych testów jednostkowych nie ma w naszym projekcie zbyt wiele, to najlepiej przejść przez proces wyznaczania kontraktu każdego komponentu wchodzącego w skład podsystemu. Łatwo będzie wychwycić punkty wspólne dla kilku komponentów, czyli naszą tytułową integrację.

Nie zaszkodzi również odpalić demo aplikacji oraz spojrzeć na nią z punktu widzenia użytkownika. To pozwoli nam nabrać większego dystansu do kodu, co pozytywnie wpłynie na implementację samych przypadków testowych. W testach integracyjnych warto odejść od wewnętrznej logiki komponentów na rzecz efektów końcowych ich pracy: czy (a nie jak) osiągnęliśmy oczekiwane rezultaty przy wyświetlaniu zawartości bądź obsłudze zdarzenia?

Zastosowanie powyższych wskazówek pozwoliło mi wypisać następujące wymagania co do crypto-trackera:

  • Aplikacja wyświetla animację ładowania, dopóki dane nie zostaną pobrane z serwera
  • Aplikacja wyświetla listę kryptowalut pobranych z serwera po ich załadowaniu
  • Aplikacja pozwala na przeszukiwanie listy za pomocą paska wyszukiwania

Okej, skoro już wiemy co musimy przetestować, to czas ubrudzić sobie ręce. Wracamy do Jest i Enzyme.

Dziel i rządź

Zanim zabierzemy się za implementacje przypadków testowych, rozdzielimy testy jednostkowe i integracyjne na osobne pliki. Taka separacja otworzy nam furtkę do uruchamiania wyłącznie wybranego rodzaju testów. Przy fixowaniu buga ograniczenie się do wielokrotnego wykonywania testów jednostkowych zaoszczędzi nam dużo cennego czasu. Często testy integracyjne zostawia się na koniec, gdy wszystko śmiga już w odizolowanym środowisku.

Zacznijmy od stworzenia folderu src/containers/tests, przenieśmy do niego App.test.js i zmieńmy jego nazwę na App.unit.test.js. Następnie utwórzmy plik przeznaczony do testów integracyjnych: App.int.test.js.

To nie wszystko, musimy jeszcze zmodyfikować npm scripts w package.json. Przyda nam się argument konsolowy Jest: --testPathPattern:

Jak widzisz, mamy również do dyspozycji skrypt test, który pozwoli na odpalenie wszystkich testów. Jesteśmy gotowi na każdą ewentualność.

Przed wyruszeniem w drogę należy… przygotować rusztowanie

Klasycznie czeka nas jeszcze jeden przystanek przed prawdziwym testerskim szaleństwem. Musimy przygotować funkcje pomocnicze, hooki i inne tego typu historie.

Tym razem będziemy korzystali z mount renderingu. Do sprawdzenia współpracy App i jego podopiecznych, musimy wyrenderować całe drzewo tego komponentu. Shallow rendering, wykorzystywany w testach jednostkowych, nie zdałby tutaj egzaminu, bo zwraca jedynie płytką wersję root komponentu.

Praca zespołowa pod lupą

Jesteśmy gotowi na implementację testów, które zaplanowaliśmy. Do dzieła.

  • Aplikacja wyświetla animacje ładowania, dopóki dane nie zostaną pobrane z serwera.

Tutaj wystarczą dwie asercje. Pierwsza sprawdzi, czy komponent obsługujący animację ładowania Spinner został wyrenderowany. Druga upewni nas, że lista nie wyświetla jakiegokolwiek Coina.

  • Aplikacja wyświetla listę kryptowalut pobranych z serwera po ich załadowaniu

Zacznijmy od upewnienia się, że Spinner znika po załadowaniu danych.

Mimo że kod testu sprawia wrażenie poprawnego, wysypuje się, twierdząc, że Spinner nadal jest wyświetlany przez CoinList.

Na początku byłem przekonany, że wynika to z błędu w logice samej aplikacji. W końcu od tego są testy. Jednak szybka randka z debuggerem (oprócz niesamowitych wspomnień) doprowadziła mnie do innych wniosków. Wszystko działa jak należy. Zabrałem się za debugowanie samych testów: stan appWrappera w momencie wykonywania asercji jasno wskazywał, że state.cryptos ma przypisane zmockowane dane, a isLoading jest ustawione na false.

Teraz największy hit, który wprowadził mnie w stan absolutnego zdezorientowania.

W ramach dalszego śledztwa postanowiłem porzucić wyszukane metody. Pozostała mi ostatnia deska ratunku, najpopularniejszy sposób na uzyskanie odpowiedzi na pytanie „co się tutaj od.. dzieje?” w ekosystemie JS. W ruch poszedł console.log().

Z perskeptywy czasu, nie wiem, czy to był dobry pomysł. Zgadnijcie, co zwrócił console.log(coinListWrapper.text()) – tekst wygenerowany na podstawie zmockowanych kryptowalut i ani śladu po Spinnerze.

Zrozumiałem, co się wyprawia (częściowo) dopiero po przeczytaniu tego wątku na GitHubie.

W skrócie: wrappery dzieci są „odcięte” od rodzica. Zmiany w rodzicu nie są automatycznie odzwierciedlane w wrapperze dziecka. coinListWrapper to referencja do wrappera sprzed wywołania appWrapper.update().

Byłoby to całkiem sensowne, tylko skąd taki wynik wywołania w coinListWrapper.text()>, który jednak oddaje aktualny stan rodzica. Tego nie było dane mi pojąć. Jeżeli znasz rozwiązanie tej zagadki, koniecznie daj znać w komentarzach :).

Aby uniknąć całego tego zamieszania, musimy inaczej zabrać się za uzyskiwanie dostępu do CoinList. Jak już ustaliliśmy, w asercji interesuje nas aktualny stan wrappera, a nie studiowanie zaburzeń czasoprzestrzeni. Stąd zamiast zmiennej, przyda nam się funkcja, która będzie zwracała świeżo odszukany wrapper.

Od tej pory odwołuję się do CoinList za pomocą wywołań getCoinListWrapper().

Okej, teraz jest zielono, kryzys zażegnany.

Przejdźmy do sprawdzenia, czy liczebność Coinów pokrywa się z liczebnością zmockowanych danych.

Kolejny punkt na naszej liście odhaczony.

  • Aplikacja pozwala na przeszukiwanie listy za pomocą paska wyszukiwania

Powyższe wymaganie rozbijemy na kilka przypadków testowych.

  • Aplikacja dopasowuje do frazy wyszukiwania tylko jedną walutę

  • Aplikacja dopasowuje do frazy wyszukiwania kilka walut

  • Aplikacja wyświetla wiadomość informującą o braku wyników przy braku dopasowań

  • Aplikacja wyświetla wszystkie dostepne dane po usunięciu frazy wyszukiwania

I to by było na tyle. Cały kod testów integracyjnych znajdziecie tutaj.

Podsumowanie

Gdyby nie ta zagadka rodem z Sherlocka Holmesa, to testy integracyjne poszłyby naprawdę gładko. Umiejętności, które zdobyliśmy przy pisaniu testów jednostkowych oraz znajomość API Enzyme, mocno zaprocentowało przy testach integracyjnych.

Najważniejsze sprawy, o których powinieneś pamiętać:

  • Testy integracyjne nie muszą być liczne, aby dostarczyć dużo wartości
  • Pisz funkcje pomocnicze odszukujące wrappery dzieci, zaoszczędzisz sobie nieprzyjemnych niespodzianek

Przed nami ostatni przystanek na testerskiej wyprawie: testy end-to-end. Porzucamy Enzyme i Jest na rzecz cypress. Zmieni się forma i narzędzie, ale wszystko sprowadzi się do odtworzenia w przeglądarce scenariusza zbudowanego na podstawie przeprowadzonych dzisiaj testów integracyjnych.

Podobał Ci się dzisiejszy artykuł? Udostępnij go w Twoich ulubionych mediach społecznościowych. Może 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-logorawpixel

Jak pisać testy jednostkowe komponentów React z Jest i Enzyme

Jak to bywa z początkami, są trudne. Przypomniałem sobie o tym podczas pisania pierwszych testów jednostkowych w React. Dręczyło mnie wiele pytań: co powinienem testować? A co zostawić w spokoju? Czy moje testy zbytnio skupiają się na wewnętrznej mechanice komponentu? A może przez brak doświadczenia pomijam istotne elementy
interfejsu? Takie wątpliwości w połączeniu z rozbudowanym API Jest i Enzyme to idealna recepta na sparaliżowanie nawet najśmielszego adepta testowania. W tym artykule zaprezentuję metodę, która pozwoliła przełamać testerską blokadę i ruszyć z praktyką. (więcej…)

Lista materiałów, których potrzebujesz do skutecznej nauki Reacta

Stawiasz pierwsze kroki w ekosystemie Reacta? A może początki masz już za sobą, ale część zagadnień jest dla Ciebie problematyczna? Tak się składa, że w czasie wolnym od tworzenia kolejnych komponentów, udało mi się odszukać wiele wartościowych materiałów. Pozwolą Ci na skuteczną naukę, niezależnie od dotychczasowego poziomu umiejętności.

(więcej…)

Przejście otoczone stosem książek

Co musisz wiedzieć, żeby zacząć testowanie komponentów React?

Wprowadzenie testów to najlepszy sposób na podniesienie jakości i niezawodności tworzonego przez Ciebie oprogramowania. Jak pokazała przeprowadzona przeze mnie ankieta, czytelnicy bloga świetnie zdają sobie sprawę. Mimo najlepszych chęci, pierwsze kroki w świecie testowania mogą być przytłaczające. Terminologia, konfiguracja środowiska, o samym pisaniu testów nie mówiąc. Tym razem udowadniam, że to żadne rocket science. Po przeczytaniu tego wpisu będziesz mógł zabrać się za testowanie komponentów React.

(więcej…)

Śledzenie kursów kryptowalut czyli AJAX w React – PPwRJS #10

Zbliżamy się do zakończenia pracy nad trackerem kryptowalut. Jedyne, czego brakuje do zrealizowania założeń, to wprowadzenie aktualnych danych o kursach z zewnętrznego serwera. Do realizacji tego celu posłuży nam AJAX oraz API CoinMarketCap. React jest biblioteką skoncentrowana na View, stąd na próżno szukać wbudowanego rozwiązania do obsługi asynchronicznego JSa. Otwiera to przed nami szereg możliwości, co ma tyle samo wad (co wybrać?!), co zalet (to co Ci odpowiada! ;)). Zacznę od omówienia dwóch najpopularniejszych sposobów na AJAX w React czyli natywnego fetch i biblioteki axios. Następnie przejdziemy do dopieszczania trackera kryptowalut świeżymi danymi wprost z serwerownii.

(więcej…)

Zatłoczone skrzyżowanie w Tokio

Cykl życia komponentu – PPwRJS #9

W czym każdy z nas, programistów, przypomina komponent React? Człowiek przechodzi przez dzieciństwo, dojrzałość i starość. Tak się składa, że bardzo podobny cykl życia charakteryzuje komponenty. Możemy w ich przypadku wydzielić okresy Mounting (dzieciństwo), Updating (dojrzałość) i Unmounting (starość). Taki systematyczny podział, wspólny dla każdego przedstawiciela swojego rodzaju, pozwala podzielić obowiązki, którym każda jednostka będzie musiała sprostać w poszczególnych okresach swojego żywota. Zdecydowanie łatwiej poradzić sobie w skomplikowanym świecie z informacją, że nauka chodzenia (w przypadku ludzi) czy inicjalizacja stanu (w przypadku komponentów) powinna być jedną z pierwszych spraw, którym poświęcimy uwagę. W internecie roi się od rad odnośnie do tego, jak radzić sobie z cyklem życia homo sapiens. Ja ograniczę się do złotych rad zapewniających właściwy rozwój Twoich komponentów.

(więcej…)

Odwrócona dziewczyna w żółtej bluzie, z różowym plecakiem, siedząca na murku

Stylowanie w React – PPwRJS #8

React wywrócił świat front-endu do góry nogami. Pierw za sprawą JSX doszło do zatarcia granic pomiędzy warstwą struktury (HTML), a warstwą zachowania (JS). Nie trzeba było długo czekać, aby ten trend rozprzestrzenił się na warstwę prezentacji. Zamieszanie na linii React-CSS trwa w najlepsze od trzech lat. Najpopularniejsze sposoby na stylowanie aplikacji w ekosystemie Reacta opisałem na przykładzie trackera kryptowalut.

(więcej…)

Dłoń wyjmująca plik płyt winylowych z pudełka.

Wyszukiwarka kryptowalut (część 2): Obsługa zdarzeń

Przyszedł czas na zbudowanie najciekawszego komponentu w całym trackerze kryptowalut. Jest nim SearchBar. Całe działanie tego komponentu opiera się o obsługę zdarzeń. Z wpisu dowiesz się, jakie są różnice pomiędzy zdarzeniami w React i DOM API. Dużo uwagi poświęciłem wiązaniu this, które może sprawić Ci niemiłą niespodziankę przy przekazywaniu referencji do funkcji w props. Najlepsze zostawiłem na koniec, w ostatniej części wpisu implementujemy algorytm wyszukiwarki.

(więcej…)

Przecinające się mosty widziane z lotu ptaka

Wyszukiwarka kryptowalut (część 1): Wyświetlanie warunkowe

Marny użytek z trackera kryptowalut bez możliwości przeszukiwania jego zawartości. Czas zrobić z tym porządek i tchnąć w aplikację odrobinę życia. Zbudujemy komponent wyszukiwarki. To świetna okazja, żeby rzucić okiem na wyświetlanie warunkowe oraz obsługę zdarzeń. Są to tematy znane każdemu programiście JS, przekonamy się jak prezentują się w świecie Reacta.

(więcej…)

Dłoń trzymająca pióro nad kartką z listą zadań

Dynamiczna lista kryptowalut – Pierwszy projekt w ReactJS #5

Naszedł czas na zabranie się za rdzeń trackera kryptowalut. Jest nim nic innego jak wyświetlanie kursów setek kryptowalut. Do tej pory samodzielnie wyświetlaliśmy każdą instancję komponentu Coin. Takie rozwiązanie ma kilka oczywistych wad. Narusza zasadę DRY, jest podatne na błędy przy referowaniu do kolejnego wycinka state, i przede wszystkim, kompletnie się nie skaluje. Musi być lepsze rozwiązanie. Jest nim połączenie możliwości programowania funkcyjnego oraz JSX. (więcej…)