W tym artykule na podstawie przykładu przedstawię czym jest Stub, Mock oraz Spy. Wykorzystam w tym celu biblioteke Mockito2. Stub, Mock i Spy są to obiekty pomocnicze przy tworzenie testów jednostkowych które zastępują nam prawdziwe klasy. Gdy nie mamy dostępu do implementacji klasy lub łączymy się z zewnętrznymi API, bazami danych, aby testy jednostkowe były przeprowadzane prawdiłowo wykorzystujemy w tym celu właśnie obiekty pomocnicze w postaci Stub, Mock i Spy. Nie używamy rzeczywistych obiektów z powodu kilku kwestii:
- testy jednostkowe stają się testami integracyjnymi
- testy jednostkowe zależą od zewnętrznych usług, które czasami mogą nie działać
- zewnętrzne usługi mogą zwracać różne dane w zależności od czasu ich wywołania
- wydłuża się czas trwania testów jednostkowych a te powinny być jak najkrótsze (odpytywanie zewnętrznego API zabiera czas)
Przyjrzymy się pierwszemu obiektowi jakim jest Stub. Służą one do imitacji rzeczywistej implementacji, a ich jedynym zadaniem jest zwrócenie konkretnej wartości. Załóżmy, że prowadzimy jakiś serwis w którym użytkownicy mogą zakładać konta. Takie konta przechowujemy w bazie danych, a za pomocą klasy AccountService pobieramy te dane z bazy i możemy je filtrować. Poniżej przykładowy Stub i test jednostkowy z jego wykorzystaniem.
public class AccountRepositoryStub implements AccountRepository{
@Override
public List<Account> getAllAccounts() {
Account account1 = new Account(login1, password1);
Account account2 = new Account(login2, password2);
Account account3 = new Account(login3, password3);
return Arrays.asList(account1,account2,account3);
}
}
@Test
void shouldReturnAllAccounts() {
//given
AccountRepository accountRepositoryStub = new AccountRepositoryStub();
AccountService accountService = new AccountService(accountRepositoryStub);
//when
List<Account> accounts = accountService.fetchAccounts();
//then
assertThat(accounts.size(), is(3));
}
Przetestowana została funkcja klasy AccountService do zwracania wszystkich kont użytkowników. W tym celu w stworzonym stubie zaimitowaliśmy działanie funkcji pobierania kont tworząc trzy konta o losowych wartościach które nastepnie były zwracane. Stuby tworzone są aby zwracać konkretne wartości dlatego używane są przy prostych metodach, natomiast przy większych projektach może się okazać, że potrzebowalibyśmy bardzo dużo stubów i kod bardzo by sie rozrastał. Dlatego przejdźmy do poznania kolejnego obiektu pomocniczego, a jest nim Mock. Jest to obiekty symulujący rzeczywisty obiekt. Mocki pozwalają na sprawdzanie spodziewanych interakcji w projekcie oraz czy rzeczywiście one wystąpiły. Poniżej test z Mockiem klasy AccountRepository i sprawdzeniem czy podczas wywoływania funkcji fetchAccounts() na klasie AccountService wykonuje się metoda getAllAccounts() z AccountRepository.
@Test
void shouldCallGetAccountsMethod() {
//given
AccountRepository accountRepository = mock(AccountRepository.class); // mockowanie klasy
AccountService accountService = new AccountService(accountRepository);
//when
List<Account> accounts = accountService.fetchAccounts();
//then
then(accountRepository ).should().getAllAccounts();
}
Ostatni obiekt pomocniczy to Spy. Jest on połączeniem rzeczywistego obiektu z mockiem. Jego przewagą jest to, że może on zarówno symulować metody jak i korzystać z rzeczywistych metod obiektu. Załóżmy, że w klasie Math mamy funkcję liczącą pole prostokątu. Zobaczmy na jej przykładzie jak działa Spy.
@Test
void shouldReturnCorrectArea() {
//given
Math math= spy(Math.class);
given(math.getLength()).willReturn(15);
given(math.getWidth()).willReturn(10);
//when
int area= math.calculateArea();
//then
then(math).should().getLength();
then(math).should().getWidth();
assertThat(area, equalTo(150));
}
Widzimy, że symulujemy zwróceni wartości długości i szerokości klasy math, a zarazem używamy rzeczywistej metody do obliczania pola na tych wartościach. Dodatkowo sprawdzamy czy po wywołaniu metody obliczania pola wywoływane są metody poboru długości i szerokości przez math oraz czy wynik zgadza się z założeniami.
Podsumowując najczęściej stosowanymi obiektami są Mock oraz Spy. Stuby działają tylko dla prostych projektów. W praktyce powinno się mockować klasy będące różnego rodzaju serwisami, repozytoriami.