Tworzenie mocków w Mockito
Spis treści
Korzystając z Mockito jest kilka sposobów na tworzenie mocków. Spójrzmy, co jest dostępne.
Metoda statyczna Mockito.mock()
To zdecydowanie najprostszy do zrozumienia mechanizm.
@Test
public void shouldNotCallApiIfNotNeeded() {
// given
String email = "user@example.com";
String username = "magiczny_krzysztof";
String avatar = "https://www.funny.pl/images/items/d7a84628c025d30f7b2c52c958767e76.jpg";
RandomUserService randomUserService = Mockito.mock(RandomUserService.class);
UserBuilder userBuilder = new UserBuilder(randomUserService);
// when
userBuilder.createUser(email, username, avatar);
// then
Mockito.verify(randomUserService, Mockito.never()).fetchRandomUser();
Mockito.verifyNoInteractions(randomUserService);
}
Mock jest tworzony po wywołaniu metody
RandomUserService randomUserService = Mockito.mock(RandomUserService.class);
Najczęściej stosuje się go w połączeniu z statycznym importem
import static org.mockito.Mockito.mock;
co w efekcie daje:
RandomUserService randomUserService = mock(RandomUserService.class);
Oprócz tego istnieją przeciążone wersje tej metody:
- mock(Class<T> classToMock, String name) - przyjmuje nazwę, która jest później używana przy debudowaniu
- mock(Class<T> classToMock, Answer defaultAnswer) - przyjmuje interfejs funkcyjny Answer, który pozwala na nadpisanie domyślnej odpowiedzi
- mock(Class<T> classToMock, MockSettings mockSettings) - przyjmuje dodatkowo MockSettings, które pozwala dodatkowo skonfigurować tworzony obiekt
Adnotacja @Mock i MockitoAnnotations.openMocks()
Jeśli w większości metod testowych używamy tych samych mocków, a tak jest najczęściej, to można ten mock zadeklarować jako pole klasy:
class UserBuilderOpenMocksTest {
@Mock RandomUserService randomUserService;
private UserBuilder userBuilder;
private AutoCloseable closeable;
@BeforeEach
void init() {
closeable = MockitoAnnotations.openMocks(this);
userBuilder = new UserBuilder(randomUserService);
}
@AfterEach
void cleanup() throws Exception {
closeable.close();
}
@Test
public void shouldNotCallApiIfNotNeeded() {
// given
String email = "user@example.com";
String username = "magiczny_krzysztof";
String avatar = "https://www.funny.pl/images/items/d7a84628c025d30f7b2c52c958767e76.jpg";
// when
userBuilder.createUser(email, username, avatar);
// then
Mockito.verify(randomUserService, Mockito.never()).fetchRandomUser();
Mockito.verifyNoInteractions(randomUserService);
}
}
Mamy tutaj pole:
@Mock RandomUserService randomUserService;
Przed uruchomieniem testu jego wartość to null, a mock jest tworzony i przypisywany dopiero w momencie wywołania MockitoAnnotations.openMocks(). To natomiast zwraca AutoCloseable, na którym wg dokumentacji należy wywołać metodę close() po wykonaniu testu. Robimy tak więc w metodzie oznaczonej @AfterEach. Warto tutaj też zwrócić uwagę na to, że klasa, którą testujemy (tutaj to UserBuilder) również stała się polem klasy, przez co dostęp do niej mamy w każdej metodzie testowej i nie ma potrzeby każdorazowo tworzyć ją na nowo.
W powyższym przykładzie trochę słabe jest to, że w teście pojawia się dodatkowe pole AutoClosable. Na szczęście tę część z zamykaniem możemy wydzielić do osobnej klasy, po której mogą dziedziczyć inne testy:
public class BaseTest {
private AutoCloseable closeable;
@BeforeEach
final void setupMocks() {
closeable = MockitoAnnotations.openMocks(this);
}
@AfterEach
void cleanup() throws Exception {
closeable.close();
}
}
class UserBuilderOpenMocksWithBaseTest extends BaseTest {
@Mock RandomUserService randomUserService;
private UserBuilder userBuilder;
@BeforeEach
void init() {
userBuilder = new UserBuilder(randomUserService);
}
@Test
public void shouldNotCallApiIfNotNeeded() {
// given
String email = "user@example.com";
String username = "magiczny_krzysztof";
String avatar = "https://www.funny.pl/images/items/d7a84628c025d30f7b2c52c958767e76.jpg";
// when
userBuilder.createUser(email, username, avatar);
// then
Mockito.verify(randomUserService, Mockito.never()).fetchRandomUser();
Mockito.verifyNoInteractions(randomUserService);
}
}
Adnotacja @Mock i rozszerzenie MockitoExtension
Poprzednie podejścia były niezależne od frameworka wykorzystywanego do uruchamiania testów. Jeśli jednak korzystamy to JUnit 5 to możemy skorzystać z rozszerzenia MockitoExtension. Najpierw jednak należy dodać kolejną zależność, w której owo rozszerzenie się znajduje:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-junit-jupiter</artifactId>
<version>4.8.0</version>
<scope>test</scope>
</dependency>
W JUnit 5 rozszerzenia dodajemy do klasy z testami za pomocą adnotacji @ExtendWith wskazując co dodać.
@ExtendWith(MockitoExtension.class)
class UserBuilderMockitoExtensionTest {
@Mock RandomUserService randomUserService;
private UserBuilder userBuilder;
@BeforeEach
void init() {
userBuilder = new UserBuilder(randomUserService);
}
// testy
}
Dzięki temu nie musimy sami wywoływać openMocks() - zostanie to wykonane przez rozszerzenie. Co ciekawe powyższy fragment można jeszcze bardziej skrócić korzystając z @InjectMocks:
@ExtendWith(MockitoExtension.class)
class UserBuilderMockitoExtensionTest {
@Mock RandomUserService randomUserService;
@InjectMocks UserBuilder userBuilder;
@Test
public void shouldNotCallApiIfNotNeeded() {
// given
String email = "user@example.com";
String username = "magiczny_krzysztof";
String avatar = "https://www.funny.pl/images/items/d7a84628c025d30f7b2c52c958767e76.jpg";
// when
userBuilder.createUser(email, username, avatar);
// then
Mockito.verify(randomUserService, Mockito.never()).fetchRandomUser();
Mockito.verifyNoInteractions(randomUserService);
}
}
@InjectMocks powoduje automatyczne wstrzyknięcie mocków do wskazanej klasy. Natomiast szczegóły działania tego mechanizmu przekraczają zakres tego wpisu.
Podsumowanie
Poznaliśmy 3 sposoby tworzenia mocków. Najwygodniejszy jest z użyciem MockitoExtension, co jednak nie znaczy, że pozostałe nie są stosowane.
Dyskusja i komentarze
Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.