Mockito w akcji, czyli wykorzystanie frameworka do lifetimera

Napiszemy sobie serwis do obsługi czasu, będziemy go dodawać do repozytorium. Nie zdecydowałem jeszcze jaki nośnik pamięci to będzie dlatego do wytestowania wykorzystamy framework Mockito. Jednocześnie niektóre czasy będą się składały na czasy ogólniejsze.

Zacznijmy od stworzenia atrapy obiektu interfejsu Repository. Interfejs na razie jest pusty ponieważ nie wiem jakie metody będziemy potrzebować. Wykorzystamy do tego adnotację @Mock, ktory działa na zasadzie Dependency Injection.

Następnie utworzymy obiekt naszego serwisu. Obiekty tego typu z założenia nie powinny przechowywać stanu wewnętrznego. Wykorzystując to stworzymy jeden obiekt dla wszystkich testów. Potrzebujemy jednocześnie, żeby stworzony w teście Mock został wstrzyknięty do naszej klasy serwisowej. Używamy do tej czynności adnotacji @InjectMocks.

Ok. Mamy już stworzone puste obiekty. Chcielibyśmy jednak, aby coś one robiły. Bierzmy się więc do roboty. Napiszmy test, że wraz z użyciem w serwisie metody "add" z obiektem Time, uzyskamy efekt, że ten obiekt został zapisany do repozytorium. Ustalmy sobie, że zapis będzie po użyciu metody save.

Wytłumaczę co tutaj stworzyliśmy. W setUp inicjalizuje się obiekt, będzie on nowy przy wywołaniu każdego testu. Dzieje się tak, ponieważ jest nad funkcją użyta adnotacja @Before. Następna metoda to tearDown. Tutaj resetuje się Mock repozytorium po każdym teście. Służy do tego funkcja reset(Mock mock). Analogicznie do poprzedniego przypadku wykonuje się to po testach, ponieważ jest umieszczona adnotacja @After. Wykorzystujemy to, ponieważ brak resetu mógłby spowodować napisanie testów od siebie zależnych lub koligujących ze sobą.

Przejdźmy teraz do samego testu. Pierwsza linijka to stworzenie nowej atrapy obiektu klasy Time. Dlaczego mock, a nie normalny obiekt? Ponieważ w trakcie wykonywania veryfi, możemy jednoznacznie sprawdzić czy ten konkretny obiekt został zapisany do repozytorium. W tym przykładzie akurat jest to niepotrzebne, ponieważ equals w obiecie Time jest na razie dziedziczony z klasy Object, ale dobre praktyki używania Mockito warto sobie wpoić od początku.

Następnie wywołujemy naszą metode, której poprawność weryfikujemy. Tutaj wywołujemy ją z naszym wcześniej utworzonym mockiem. Pomoże nam to w weryfikacji.

Po wykonaniu funkcji czas na sprawdzenie czy wykonała się poprawnie. Wiemy, że powinna wywołać funkcje save(Time time) z naszym mockiem jeden raz. Sprawdzamy, więc to w naszym teście. Używamy do tego funkcji Mockito.veryfi(Mock). Domyślnym sprawdzeniem tej funkcji jest jednorazowe wykonanie (times(1)). Do sprawdzenia innych warunków używamy jako drugi argument VerificationMode. Zdefiniowane są never(), only(), times(int wantedNumberOfInvocations), atLeast(int minNumberOfInvocations), atLeastOnce(), atMost(int maxNumberOfInvocations), calls(int wantedNumberOfInvocations). Po wykonaniu verifi z odpowiednim walidatorem oraz mockiem na zwróconym obiekcie wykonujemy metodę, którą chcemy wytestować.

Implementacja logiki do tego testu wygląda następująco:

Zaimplementujmy sobie teraz dodawanie wartości czasowych do naszego obiektu. Pola, które klasa Time powinna posiadać to dni, godziny, minuty i sekundy. Jeszcze lepiej byłoby, gdyby sekundy były przeliczane na minuty, jeżeli przekracza powyżej wartości 59 i analogicznie dla innych pól. Dodatkowo dobrą funkcjonalnością byłoby dodawanie i odejmowanie czasu od innego czasu. Kolejnym założeniem będzie, że nie ma czasów minusowych. Najmniejszy czas jaki może posiadać obiekt to czas 0. Ok, założeń jest dużo. Umieszczony jest tutaj efekt końcowy napisany w stylu TDD.

Załóżmy, że chcielibyśmy ten czas dodawać do czasu ogólnego np. jakiejś kategorii. Jeszcze nie posiadamy podziału na kategorię, ale możemy założyć, że czas będzie miał rodzica, który będzie właśnie czasem ogólniejszym.

Test tego przypadku będzie wyglądał następująco:

W teście pojawiła się nowa funkcjonalność Mockito. Funkcja when(T methodCall), która pozwala na przypisanie odpowiedniego zachowania dla podanej funkcji. Warunek jest taki, że musi być funkcją atrapą (stubbing). Możemy to osiągnąć poprzez stworzenie atrapy obiektu (mock(T class)) lub obiektu szpiegowanego (spy(T object)). Po wykonaniu tej funkcji możemy przypisać, co ma być zwracane. Zdefiniowane są then(Answer<?> answer), thenAnswer(Answer<?> answer), thenCallRealMethod(), thenReturn(T value), thenReturn(T value, T... values), thenThrow(Class<? extends Throwable> throwableType), thenThrow(Class<? extends Throwable> toBeThrown, Class<;? extends Throwable>... nextToBeThrown) i thenThrow(Throwable... throwables).

Podsumowując powiedzieliśmy testowi, że jeżeli funkcja otrzyma parentId, który został przekazany to ma zwrócić naszego zamockowanego Time parent.

Ciekawą funkcją jest jeszcze UUID.randomUUID(), tworzy ona unikatową wartość z bardzo małym prawdopodobieństwem wygenerowania identycznego. W celu nie wymyślania identyfikatora, użyliśmy właśnie tej opcji do genereacji jego.

Logika stworzona w celu zaspokojenia testu wygląda następująco:

To tyle na dzisiaj polecam zapoznać się z teorią na temat frameworku Mockito. Projekt będzie kontynuowany i jest umieszczony na github.

Comments

Popular posts from this blog

Why TDD is bad practice?

How correctly imitate dependencies?

Software development using Angular 5 with Spring Boot