Angular – Testowanie zapytań HTTP

Czy kiedykolwiek miałeś problem z przetestowaniem serwisów w Angularze? W jaki sposób poprawnie wykonać testy jednostkowe usług HTTP? Na to pytanie odpowie nam Tomasz Borowski, którego zaprosiłem do wpisu gościnnego na moim blogu.

 

Tomasz Borowski
Tomasz Borowski

Tomasz Borowski to frontend developer z ponad 10 letnim doświadczeniem programistycznym, pracujący na co dzień przy implementacji złożonych aplikacji biznesowych w frameworku Angular. Tomasz był wielokrotnie speakerem na konferencjach lokalnych oraz międzynarodowych – w tym Agile Lean Europe oraz Agile Cambridge. Od ponad roku prowadzi także serię warsztatów szkoleniowych Angular in Space.

Skoro zapoznaliśmy się już z sylwetką autora, czas przejść do meritum 😉

Testowanie zapytań HTTP w Angular

Jedną z najistotniejszych odpowiedzialności usług Angularowych jest przygotowywanie danych na potrzeby komponentów. Najczęściej takie dane są pozyskiwane za pomocą zapytań HTTP, a następnie przetwarzane przy pomocy operatorów RxJS. Efektem końcowym jest zgrabne wykorzystanie gotowych danych w komponencie, który nie zna “szczegółów technicznych” ich pozyskania.

 

Podczas pisania testów jednostkowych dla takiej usługi wykonującej zapytania HTTP będziemy chcieli zasymulować (zamockować) wysyłanie zapytań tak, aby nie było konieczności odpytywania prawdziwego API. Rozważmy zatem dwa sposoby na poradzenie sobie w testach z zależnością HttpClient (dostępną w Angular od wersji 4.3) poprzez wykorzystanie metody spyOn z frameworka Jasmine oraz wykorzystanie HttpClientTestingModule.

 

Co potrzebujesz wiedzieć?

Zakładamy, że potrafisz już korzystać z usługi HttpClient oraz znasz podstawy testowania jednostkowego z biblioteką Jasmine. Jeśli nie to zachęcamy do zapoznania się z guidem do HttpClient oraz wprowadzeniem do Jasmine.

 

Co będziemy testować?

Przedmiotem naszych testów będzie usługa CarService, która udostępnia metodę getCars(query) do wyszukiwania samochodów za pomocą zapytań HTTP. Dane przychodzące z API są zamieniane na obiekt (za pomocą operatora map), który zawiera liczbę zwróconych wyników oraz kolekcję samochodów. W razie niepowodzenia w pobieraniu danych reemitujemy treść komunikatu błędu za pomocą operatora catchError oraz _throw. Zatem czeka nas rozważenie w testach dwóch przypadków: gdy zapytanie powiodło się oraz gdy zapytanie nie powiodło się.

 

Poniższa implementacja wykorzystuje tzw. pipeable operators, które są dostępne w RxJS od wersji 5.5, która jest wspierana przez Angular od wersji 5.0.0. Z racji na obecność słów zastrzeżonych część operatorów jest dostępna pod innymi nazwami: catch -> catchError, throw -> _throw. Więcej o pipeable operators możesz znaleźć tutaj.

Testy z wykorzystaniem funkcji spyOn

Przystępując do jednostkowego testowania usługi CarService musimy “odciąć się” od jej zależności, które często wprowadzają własne, dodatkowe zależności. Nie inaczej jest w przypadku usługi HttpClient, dlatego na potrzeby testów utworzymy sobie prostą klasę FakeHttp, która powinna mieć zgodny interfejs z klasą HttpClient.

Następnie wykorzystując klasę TestBed konfigurujemy moduł testowy i łapiemy referencje do zarejestrowanych usług. Zwróć uwagę, że pod nazwą HttpClient w rzeczywistości rejestrujemy uproszczoną klasę FakeHttp.

Przystępując do testowania metody getCars(query) mamy do rozważenia dwa przypadki: gdy request się powiedzie oraz gdy request się nie powiedzie. W pierwszym przypadku wykorzystamy funkcję spyOn z Jasmine i zamockujemy wynik wykonania metody http.get by był to strumień RxJS zawierający kolekcję samochodów. Do utworzenia strumienia wykorzystujemy operator tworzący of. Więcej na temat możliwości funkcji spyOn znajdziesz tutaj.

Mając odpowiednio przygotowane warunki testowe możemy zweryfikować czy wykonanie getCars(query) wykona metodę http.get z poprawnymi parametrami oraz czy zwrócony strumień będzie zawierał odpowiednio przygotowane dane, za co jest odpowiedzialny operator map.

Drugim przypadkiem występującym w metodzie getCars(query) jest niepowodzenie requesta. W celu przygotowania warunków testowych ponownie korzystamy z funkcji spyOn, ale tym razem mockujemy metodę http.get by zwracała błąd w strumieniu. Do utworzenia takiego strumienia wykorzystujemy operator tworzący _throw.

W przypadku niepowodzenia requestu będziemy chcieli sprawdzić, czy zwracany jest komunikat błędu w strumieniu RxJS – a więc czy poprawnie wykorzystaliśmy operator catchError.

W ten sposób solidnie przetestowaliśmy metodę getCar(query) z wykorzystaniem funkcji spyOn do mockowania wykonań metody http.get. Istnieje także inne podejście do tego samego tematu, wykorzystujące moduł HttpClientTestingModule.

Testy z wykorzystaniem HttpClientTestingModule

Moduł HttpClientTestingModule został dodany do Angulara, by uprościć testowanie usług wykorzystujących usługę HttpClient. Na początku musimy skonfigurować za pomocą klasy TestBed moduł testowy, który będzie importował HttpClientTestingModule. Wśród usług, jakie rejestruje ten moduł, jest usługa HttpTestingControler, którą wykorzystamy do sprawdzania wychodzących zapytań, a także do definiowania zwracanych odpowiedzi.

Następnym krokiem będzie przetestowanie przypadku, gdy request kończy się powodzeniem. W ramach przygotowania warunków testowych wystarczy, że zdefiniujemy kolekcję danych, jakie będą zwracane w odpowiedzi na zapytanie

Nasz test będzie weryfikował czy getCars(query) zwraca strumień z dokładnie takim obiektem, jaki sobie zaplanowaliśmy. W podejściu wykorzystującym HttpClientTestingModule najpierw wykonujemy kod powodujący zapytanie, a następnie za pomocą metody httpMock.expectOne wybieramy jaki request chcemy zbadać. Aby zdefiniować odpowiedź na wybrany request korzystamy z metody flush – dopiero po jej wykonaniu zostaną sprawdzone nasze oczekiwania znajdujące się w subscribe. Kod odpowiedzialny za obsługę zapytania możemy także przenieść do metody afterEach, dzięki czemu zyskamy na czytelności testów.

Pozostał nam do sprawdzenia drugi przypadek, w którym request kończy się niepowodzeniem. Tutaj wykorzystujemy metodę error by odpowiedzieć na zapytanie błędem. Wewnątrz subskrypcji weryfikujemy czy w strumieniu zwracany jest odpowiedni komunikat błędu.

W ten sposób dokładnie przetestowaliśmy metodę getCar(query) z wykorzystaniem modułu HttpClientTestingModule. Więcej o możliwościach tego modułu możesz przeczytać tutaj.

 

Podsumowanie

Bardzo istotną częścią testowania jednostkowego jest umiejętne zastąpienie zależności klasy, którą mamy zamiar przetestować. W przypadku usługi HttpClient możemy to zrobić na co najmniej dwa sposoby: wykorzystując funkcję spyOn z biblioteki Jasmine lub wykorzystując moduł HttpClientTestingModule. To drugie rozwiązanie dostarczane jest wraz z frameworkiem Angular i eliminuje konieczność ręcznego tworzenia strumieni zawierających odpowiedź na request. Dodatkowo jeśli nasza aplikacja korzysta z interceptorów, to testowanie zapytań HTTP będzie znacznie wygodniejsze przy wykorzystaniu HttpClientTestingModule. W przypadku mniej zaawansowanych zastosowań usługi HttpClient oba podejścia są poprawne, a jego wybór sprowadza się do osobistych preferencji.


Duże podziękowania dla Tomasza za bardzo merytoryczny wpis!

Oby od dzisiaj testowanie usług HTTP, nie miało przed Tobą tajemnic 🙂 Chciałbym również dodać, że wraz z Tomaszem pracujemy wspólnie od pół roku nad projektami dla branży farmaceutycznej. Ogromna wiedza Tomasza z Angulara zrobiła na mnie duże wrażenie, stąd śmiało mogę zareklamować szkolenia prowadzone przez Tomasza pod marką Angular In Space.

www.angular-in-space.pl

https://www.facebook.com/angularinspace/

Zatem jeśli planujesz doszlifować swoją wiedzę za pomocą profesjonalnego szkolenia, zajrzyj na stronę i zapoznaj się z dostępnymi terminami szkoleń w całej Polsce.

Z radością również informuję, że Angular In Space zostaje partnerem społecznym bloga angular.love, chcemy się nawzajem wspierać w promocji Angulara w Polsce.

No cóż! to tyle na dzisiaj, a może to Ty chciałbyś wystąpić z kolejnym wpisem gościnnym? 😉 zapraszam do kontaktu:

nastalytomasz@gmail.com

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *