Tworzenie mocków w Mockito

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.