Mockito – czyli jak wycisnąć cytrynę, której nie mamy
Czym jest Mockito? Nie, nie mam na myśli zielonkawego napoju z palemką. Mam na myśli narzędzie Javy ułatwiające pisanie testów jednostkowych. Jak sama nazwa wskazuje, narzędzie to (a właściwie framework), pozwala tworzyć mocki, łatwo i przyjemnie.
Czym są tajemnicze mocki?
Można je porównać do manekinów podczas testów zderzeniowych aut. Dzięki nim możemy testować wywierane na nie siły i testować bezpieczeństwo samochodów, bez poświęcania żywych ludzi.
Mocki z kolei pozwalają na stworzenie atrapy obiektu i bezpieczne obserwowanie zachowania w testowanej sytuacji, z uwzględnieniem relacji między różnymi obiektami. A to wszystko bez konieczności uruchamiania rzeczywistej implementacji.
Przykładowo testując aplikację do nawigacji w terenie, możemy stworzyć mocka usługi pobierającej aktualną pozycję z GPS. Dzięki temu, nie musimy mieć łączności z satelitą, ba nie musimy się przemieszczać, aby testować aplikację w różnych lokalizacjach. Nasz mock, będzie zwracał symulowaną pozycję GPS, która posłuży do testowania serwisu do nawigacji.
W analogiczny sposób możemy testować aplikację bazodanową, udając serwis bazy danych, aplikację komunikującą się z Internetem, udając serwis dostępu do Internetu lub po prostu symulując nieistniejącą jeszcze implementację interfejsu.
Czym jest Mockito?
Mockito pozwala na banalnie proste tworzenie mocków i weryfikację wywieranych na nie oddziaływań.
Framework Mockito został doceniony przez społeczność Stack Overflow jako najlepsza biblioteka do tworzenia mocków, znalazł się także w rankingu 10 najpopularniejszych bibliotek wykorzystywanych w projektach zgromadzonych GitHub. Dowodzi to cenności narzędzia.
Stwórzmy zatem przykład w oparciu o wspomnianą nawigację GPS i komponenty Spring, aby zademonstrować wykorzystanie Mockito.
Nasz komponent ma za zadanie zwracać dystans określający odległość pomiędzy dwoma ostatnimi odczytami położenia geograficznego z GPS. Odległość jest zwracana za pomocą metody getDistance()
. Podczas inicjowania komponentu uruchamiamy usługę GPS i pobieramy bieżącą lokalizację. Przy wywołaniu metody getDistance()
pobieramy bieżącą lokalizację i obliczamy odległość pomiędzy kolejnymi odczytami z wykorzystaniem biblioteki Geodesy
.
Dodatkowo tworzymy interfejs, aby móc łatwo podmieniać implementacje usługi GPS i stworzyć mocka symulującego działanie GPS.
Aby przetestować działanie klasy Navigation
, musimy stworzyć mocka interfejsu GpsService
. Na początek dodajemy zależności do pom.xml
.
Biblioteka Mockito:
Biblioteka testów Spring Boot
wraz z zależnościami (junit
, assertj
, ...)
W dalszej kolejności piszemy test jednostkowy:
Podczas startu testu tworzony jest obiekt, który implementuje interfejs GpsService
. Obiekt domyślnie zawiera puste implementacje metod, które zwracają null
.
Musimy zatem określić jego zachowanie poprzez metodę when().
Powyższy fragment kodu należy rozumieć następująco:
zwróć kolejną pozycję z listy rozpoczynając od pierwszej - każde kolejne wywołanie zwróci kolejną.
Jeśli na liście byłaby tylko jedna pozycja - zwrócona zostałaby zawsze ta sama.
Pierwszą pozycję pobieramy podczas inicjowania GPS, kolejną podczas wywołania navigation.getDistance()
. W ten sposób możemy symulować zachowania wielu różnych interfejsów, bez konieczności tworzenia lub uruchamiania ich implementacji.
Mockito zawiera wiele mechanizmów weryfikacji oddziaływania na mock. Jedną z nich jest weryfikacja ilości wywołań metody z mocka.
W powyższym przypadku weryfikujemy, czy metoda getCurrentLocation
została dokładnie 2 razy wywołana z dowolnym parametrem typu double
. Jeśli będzie inaczej, test się nie powiedzie.
Analogicznie możemy zweryfikować, czy getCurrentLocation
zostanie wywołana z konkretną wartością parametru, stosując metodę Mockito.eq
:
Można też zastosować bardziej zaawansowaną weryfikację z wykorzystaniem ArgumentCaptor
:
Tworzymy obiekt przechwytujący argumenty wywołania dla klasy zgodnej z typem argumentu.
Weryfikujemy, czy metoda getCurrentLocation
została wywołana dokładnie 2 razy, zbierając przy okazji parametry wywołania w naszym obiekcie przechwytującym.
Mając przechwycone parametry wywołania, możemy zweryfikować ich zgodność z założeniami. W tym przypadku podczas 2 wywołań oczekujemy, że argument wywołania będzie posiadał cały czas tę samą wartość równą 0.6.
W podobny sposób zamiast tworzyć typowy mock
na podstawie interfejsu, można stworzyć obiekt typu spy
na podstawie rzeczywistej klasy. W obiektach typu spy
wykorzystujemy realną implementację, w której możemy symulować zachowanie wybranych metod, a dla pozostałych zachować istniejącą implementację. W poniższym przykładzie zamiast interfejsu GpsService
tworzymy atrapę na bazie rzeczywistej implementacji klasy GpsServiceImpl
(która implementuje interfejs GpsService
). W ten sposób, możemy zrezygnować z użycia Mockito.when
, czyli konieczności określania zachowania, które badamy.
To oczywiście tylko niektóre z możliwości frameworka Mockito. Pozostałe zostały opisane w oficjalnej dokumentacji.