Mockito
Spis treści
Czym jest Mockito
Mockito to biblioteka, która jest aktualnie niezbędnym narzędziem do tworzenia testów jednostkowych. Pozwala na pozbycie się niechcianej zależności, żeby łatwiej móc testować w izolacji.
Przykład do przetestowania
Zacznijmy od przykładu. Mamy do przetestowania fragment aplikacji, który losuje nick oraz avatar użytkownika, jeśli nie zostały one ustawione.
User to zwykłe POJO:
Wywołanie metody:
No i właściwa metoda, którą chcemy przetestować:
W skrócie:
- Sprawdzamy, czy została podana nazwa użytkownika i avatar
- Jeśli chociaż jednego brakuje, to odpytujemy API z prośbą o wylosowanie danych użytkownika.
- To, co otrzymaliśmy, ustawiamy jako dane użytkownika (tylko w przypadku jeśli użytkownik nie ustawił tego sam)
Co przydałoby się przetestować?
- czy pola klasy User są ustawiane na takie jak przekazane do metody
- czy pola klasy User są losowane, jeśli nie zostały przekazane do metody
- fantastycznie byłoby móc upewnić się, że API nie zostało odpytane jeśli nie było takiej potrzeby (do metody trafiło zarówno avatar, jak i username)
Za pomocą samej biblioteki JUnit może być tutaj ciężko, o ile to w ogóle możliwe. Zastanówmy się jeszcze jakie problemy możemy napotkać:
- korzystamy tutaj z tzw. zewnętrznej zależności, w tym przypadku to API, na którego działanie nie mamy wpływu
- API może mieć limity, albo pobierać opłaty za korzystanie
- mogą wystąpić problemy z połączeniem internetowym
- taki test może wykonywać się długo (>100 ms) zamiast <1ms
No i tutaj pojawia się Mockito, które pozwala nam w wygodny sposób zastąpić zewnętrzną zależność. Innymi słowy, chcemy zastąpić fetchRandomUser() własną implementacją na czas testów. Ta implementacja będzie zwracała zawsze te same dane.
.png)
Zacznijmy od wyciągnięcia tej metody do osobnej klasy. Tak robi się najczęściej i jest wygodniej. Utworzymy więc klasę RandomUserService i tam przenieśmy metodę.
Klasa UserBuilder wygląda teraz tak:
Jest tutaj jeszcze jeden problem, który musimy rozwiązać przed napisaniem testu. Chodzi o tę linię:
Aktualnie UserBuilder sam tworzy RandomUserService. Może też powiedzieć inaczej: UserBuilder aktualnie decyduje jakiej implementacji RandomUserService użyć. Jeśli dodać inny sposób pobierania losowego użytkownika to wymagana będzie zmiana w UserService, co bezpośrednio uderza w Single Responsibility Principle z zasad SOLID. Możemy to w bardzo prosty sposób poprawić, dodając tę zależność do konstruktora:
Teraz UserBuilder nie decyduje o tym czy skorzysta bezpośrednio z RandomUserService, czy z jakieś nadpisanej klasy. Na diagramie można to przedstawić tak:

Będziemy mogli sobie bez problemu podmienić RandomUserService na dowolną jego implementację, co jest główną ideą Mockito.
Testujemy z użyciem Mockito
W końcu możemy przejść do pisania testów :)
Dodajmy wymagane następujące zależności:
Oprócz Mockito dodaję JUnit, oraz AssertJ, czyli bibliotekę do tzw. płynnych asercji. Napiszmy pierwszy test, na razie bardzo prosty, bez Mockito:
Sprawdzamy, czy wszystkie pola ustawiają się poprawnie jeśli nie ma potrzeby losowania danych. Teraz pora na losowanie danych.
Tutaj zatrzymamy się na chwilę, bo pojawia się pierwsze użycie Mockito.
Metoda Mockito.mock(Class<T> classToMock) zwraca tzw. mock klasy. Mock to stworzona w locie implementacja klasy, której m.in. możemy nadać zachowanie. Z punktu widzenia reszty kodu jest to po prostu zwykły obiekt i będzie traktowany jako "zwykła" klasa RandomUserService. Domyślnie mock nadpisuje wszystkie metody klasy i zawsze zwraca null. Możemy w wygodny sposób nadpisać wybrane metody:
Tutaj najciekawsza jest ostatnia linia. Poniekąd rozkazujemy temu mockowi, żeby w przypadku gdy zostanie odpytany o metodę fetchRandomUser() to ma wtedy podać nasz wcześniej przygotowany obiekt randomUserDto.
Dzięki temu zachowanie tego serwisu stało się przewidywalne i resztę testu możemy już wykonać bez większych problemów. Ważne jednak, żeby do UserBuilder przekazać nasz mock, a nie "prawdziwy" serwis;
Sprawdzanie interakcji z mockiem
Oprócz nadawania mockowi zachowania możemy również sprawdzić, czy została na nim wykonana jakaś metoda. W tym przypadku chcemy, aby odpytanie o losowe dane użytkownika odbyło się tylko w przypadku, gdy brakuje nazwy użytkownika lub avataru. W przeciwnym przypadku nie odpytujemy serwisu, bo te dane nie są nam zwyczajnie potrzebne.
Pozbądźmy się na chwilę tego ifa:
Teraz odpytanie wykona się za każdym razem. Napiszmy test, który upewni nas, że metoda fetchRandomUser() nie została uruchomiona:
Spójrz na ostatnią linię. Sprawdzamy w ten sposób, czy na danym mocku NIE została uruchomiona metoda fetchRandomUser().
Test zakończy się niepowodzeniem z informacją:
Inna opcja sprawdzenia braku interakcji na dowolnej z metod to:
Można też sprawdzać, czy było np. dokładnie 1 wywołanie metody, albo czy metoda została wywołana z odpowiednim argumentem itd. Jest to jednak poza zakresem tego wpisu.
Podsumowanie
Mockito to podstawowe narzędzie każdego programisty Java. Pozwala na szybkie i wygodne pozbycie się niechcianych zależności, a także weryfikację czy testowany kod poprawnie się zachowuje.
Kod źródłowy tego wpisu znajdziesz tutaj: https://github.com/javastartpl/examples/tree/master/junits/junits-mockito
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.