Spring Boot Test – testowanie aplikacji w Spring Boocie

Testowanie naszych aplikacji jest bardzo ważne. Nie tylko dlatego, że pozwala znaleźć błędy, ale również pomagają w utrzymaniu kodu – daje pewność, że nie zepsuliśmy czegoś przy refaktoringu, dokumentuje użycie – można zaglądnąć do kodu testów aby dowiedzieć się jak używać jakiejś biblioteki, oraz pozwalaja na szybkie i łatwe sprawdzenie zachowania biblioteki/języka. Na przykład jeśli nie jesteśmy pewni co wywoła się pierwsze – konstruktor czy przypisanie do pola klasy – możemy stworzyć prosty kod który to sprawdzi i odpalić go w testach.

Wyjście powyższego testu wygląda następująco:

Możemy z tego wywnioskować, że najpierw zostało zainicjowane pole classOne (konstruktor z ClassOne został uruchomiony), a dopiero potem wywołany konstruktor klasy ClassTwo.

Spring Boot Starter Test

Spring Boot dostarcza własną bibliotekę przeznaczoną do tworzenia testów. Dzięki niej możemy łatwo postawić kontekst Springa lub odpalić całą aplikację do testów integracyjnych. Ułatwia to bardzo życie i oszczędza czas poświęcony na przygotowanie testów. Dodając do zależności moduł starter test otrzymujemy cały zestaw użytecznych bibliotek, wśród których możemy znaleźć:

  • JUnit – Podstawowa biblioteka do uruchamiania testów,
  • Mockito – biblioteka do tworzenia Mockowych obiektów,
  • AssertJ – biblioteka zawierająca assercje,
  • Spring Test i Spring Boot Tests – zestaw narzędzi do testowania Springa i Spring Boota,

Oprócz tego pakiet zawiera jeszcze inne biblioteki, jednak wykraczają one poza temat tego postu.

Wszystkie przykłady znajdują się jak zawsze w repozytorium na GitHubie, dostępnym pod adresem: https://github.com/mloza/spring-boot-test

Aplikacja testowa

Aby w pełni zaprezentować możliwości Springa w temacie testów, zacznijmy od stworzenia bardzo prostej aplikacji testowej. Na początek zacznijmy od kontrolera który będzie nam zwracał jakiś prosty tekst.

Po uruchomieniu aplikacji i wejściu na stronę http://localhost:8080/hello aplikcaja zwróci nam tekst Hello!.

Pierwszy test

Dodajmy test który sprawdzi nam automatycznie czy zwracany tekst jest zgodny z oczekiwaniami. Możemy to zrobić na dwa sposoby. Pierwszy z nich to wstrzyknięcie kontrolera do testu i wywołanie metody. W ten sposób sprawdzimy czy Spring znalazł nasz kontroler i czy metoda wykonywana jest prawidłowo. Drugim sposobem jest wykorzystanie mockMvc i wykonanie zapytania. W ten sposób sprawdzimy dodatkowo czy prawidłowo mapujemy adres oraz czy serializacja danych działa tak jakbyśmy tego oczekiwali. W tym przypadku, lepiej skorzystać z 2 sposobu, ponieważ sprawdzamy w ten sposób więcej aspektów. Wstrzykiwanie komponentów do testu pokażemy sobie przy testowaniu serwisów.

Kod testu z użyciem mockMvc wygląda następująco:

Testy możemy uruchomić bezpośrednio z IDE, jeśli używasz IntelliJ to w widoku kodu, obok nazwy klasy lub nazwy metody, po lewej stronie będzie mała, zielona strzałka. Kliknięcie na nią spowoduje pokazanie się menu z którego możemy wybrać Run lub Debug Test. Jeśli uruchamialiśmy wcześniej test i z jakiegoś powodu się nie powiódł, strzałka będzie czerwona z wykrzyknikiem.

U mnie jest trochę więcej opcji uruchomienia, dzięki zainstalowanym dodatkom, nie powinieneś się przejmować jeśli ich nie widzisz.

Drugim sposobem jest wywołanie mvn test z lini poleceń, wtedy testy zostaną uruchomione bezpośrednio z mavena. W ten sposób zostaną wykonane wszystkie testy w projekcie których klasy kończą się nazwą Test.

Mamy tutaj 2 nowe adnotacje, @RunWith (#1) oraz @WebMvcTest (#2). Pierwsza z nich, mówi silnikowi testów (którym w przykładzie jest JUnit), aby uruchomił ten test z użyciem SpringRunnera. Dzięki temu framework wie, że to będzie test z wykorzystaniem Springa i powinien wstrzyknąć zależności. Dodatkowo również szuka innych adnotacji takich jak @WebMvcTest i podejmuje odpowiednie działania. Druga adnotacja mówi, że chcemy testować komponenty MVC więc skonfiguruje dodatkowe elementy które będą nam potrzebne, takie jak mockMvc. W tym przypadku zostanie skonfigurowany cały kontekst springowy, ale nie będzie tworzony serwer. Dzięki temu cały test uruchomi się szybciej i nie będzie potrzebował dodatkowych portów.

Spójrzmy na kod samych testów. W punkcie oznacoznym jako #3, mówimy, że chcemy dostać to co znajduje się pod adresem /hello. Następnie upewniamy się, że zapytanie zostało wykonane poprawnie i zwrócono nam kod 200 OK (#4). Ostatnim elementem jest sprawdzenie zawartości odpowiedzi, oczekujemy, że odpowiedż zawiera tekst Hello!

Jeżeli test z jakiegoś powodu nie przechodzi, możemy dodać po punkcie #3 linię .andDo(print()) dzięki której zostaną wypisane na konsole wszystkie informacje o requeście. U mnie wygląda to następująco:

Bardzo dużo informacji diagnostycznych, które pomogą nam w znalezieniu przyczyny błędu. W tym miejscu zachęcam do sprawdzenia co się stanie jeśli będziemy oczekiwać innego tekstu niż zwróci nam kontroler lub innego statusu.

Serwis

Dodajmy do aplikacji serwis który będzie zwracał dodatkowy tekst do odpowiedzi z kontrollera.

Użyjmy go w kontrolerze, tak aby pod nowym endpointem zwracał nam tekst Hello World! Nasz kontroler w tym momencie wygląda następująco:

Jeśli uruchomimy naszą aplikację i wejdziemy w przeglądarce pod adres http://localhost:8080/helloworld powinniśmy otrzymać spodziewany tekst: Hello World!.

Test kontrolera z serwisem

Jeśli w tym momencie spróbujesz uruchomić poprzedni test, skończy się on błędem mówiącym nam o tym, że nie może znaleźć Beana pasującego do pola HelloService. Dziej się tak dlatego, że adnotacja @WebMvcTest inicjalizuje tylko komponenty związane z MVC. Możemy ten problem rozwiązać na dwa sposoby, dostarczyć mockowy serwis lub zmienić adnotację. Zacznijmy od zmiany adnotacji oraz dodania testu do nowego serwisu. Zmodyfikujmy klasę testową aby wyglądała następująco:

Pojawiły się nowe adnotacje: @SpringBootTest (#1) oraz @AutoConfigureMockMvc. Pierwsza mówi nam, że chcemy testować cały kontekst springa, nie tylko MVC, a druga, że chcemy mieć nadal możliwość korzystania z MockMvc. Z tymi adnotacjami test powinien być już zielony.

Drugim sposobem jest dostarczenie mocka serwisu. W naszym przypadku serwis jest bardzo prosty i nie było sensu go mockować. Jednak gdy serwis jest skomplikowany, odwołuje się do innych serwisów lub aplikacji których nie chcemy uruchamiać, po to tylko aby sprawdzić jakąś małą część kontrolera, możemy dostarczyć zaślepkę, czyli mocka. Pozwala on także, na wykonanie dodatkowych assercji. Zacznijmy od dodania mocka do testu. Nie musimy przywracać starych adnotacji aby tego dokonać. Zmodyfikujmy naszą klasę testową do postaci:

Pojawiło nam się nowe pole (#1) z naszym HelloService, jednak adnotacja @MockBean powoduje, że zamiast prawdziwego serwisu wstawiony zostanie tam automatycznie mock. Dodatkowo wszędzie gdzie był użyty nasz serwis, zostanie on zastąpiony tym mockiem. W punkcie #2 konfigurujemy zachowanie mocka. Dosłownie mówimy, że gdy zostanie wywołana metoda getHello naszego serwisu to zwróć Mock. W punkcie #3 sprawdzamy czy rzeczywiście tak się stało i otrzymaliśmy tekst Hello Mock!.

Wspominałem wcześniej, że mocki dostarczają nam dodatkowe metody weryfikacji. Możemy na przykład sprawdzić ile razy została wywołana nasza metoda.

Nasza metoda nie przyjmuje parametrów, jednak również możemy sprawdzić przy pomocy mocka czy została wywołana z prawidłowymi parametrami. O mockach postaram sie przygotować oddzielny wpis, ponieważ jest to dość obszerny temat.

Test serwisu

Na koniec jeszcze jak sprawdzić sam serwis. Możemy wstrzyknąć go do metody testowej za pomocą adnotacji @Autowired i wykonać na nim asercje:

W tym przykładzie widzimy użycie asercji z biblioteki AssertJ. Osobiście bardzo je lubię ponieważ można tworzyć łańcuch asercji (np. asserThat(list).hasSize(2).containsExactlyInAnyOrder(„one”, „two”);), bardzo przejrzyście opisują co sprawdzają oraz posiadają bardzo dużo gotowych sprawdzianów.

Podsumowanie

Jak widzicie testowanie z użyciem narzędzi dostarczonych przez Springa jest bardzo proste i szybkie. Dzięki temu pisanie testów będzie mniej męczące i powstanie ich więcej, co przełoży się na mniej błędów. Może się wydawać, że testowanie nie jest ważnym elementem projektu, jednak przy większym refactoringu lub powrocie do kodu po czasie okaże się, że czas poświęcony na przygotowanie dobrych testów zwrócił się wielokrotnie, przez oszczędzenie go na szukaniu i naprawianiu błędów.

Michał Autor

Komentarze

    Kamil

    (2018-10-25 - 14:03)

    Dobry tutorial, dziękuję!

Dodaj komentarz

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.