Mockito — фреймворк для тестирования приложений, который позволяет легко и быстро подменять реальные объекты программы «пустышками». Такие фиктивные объекты часто называют «моками» (Mock — подражать).
Зачем используется Mockito
Инструмент упрощает разработку юнит-тестов для классов с внешними зависимостями. Фиктивная реализация интерфейса, статического метода или класса, которую производит Mockito, позволяет определить вывод конкретных вызовов методов: фиктивные классы записывают взаимодействие с системой, а тесты могут его проверить и подтвердить.
Подключение Mockito
Обычно библиотека подключается к системе сборки приложений, которой пользуется разработчик, — Maven или Gradle. В случае с Gradle установка Mockito выглядит так:
repositories { mavenCentral() }
dependencies { testImplementation «org.mockito:mockito-core:4.3.1 }
При использовании Maven библиотека добавляется в зависимости таким образом:
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.3.1</version>
<scope>test</scope>
</dependency>
В случае совместного использования JUnit 5 и Mockito в Maven также добавляется следующая зависимость:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.5.2</version>
<scope>test</scope>
</dependency>
Кроме систем сборки, библиотеку можно подключить к любой интегрированной среде разработки. Все популярные IDE — Eclipse, Android Studio, Visual Studio Code, IntelliJ IDEA — поддерживают как Mockito, так и Maven, JUnit и Gradle.
Базовые понятия тестирования
Юнит-тесты позволяют проверять поведение определенных классов или методов в отрыве от их зависимостей. Так как тестируют самые маленькие «элементы» кода, не нужно использовать настоящие реализации зависимостей. Более того, чтобы протестировать различные модели поведения, часто требуется применять немного разные реализации этих зависимостей. Традиционный подход к тестированию — создание «заглушек», конкретных реализаций интерфейса, подходящих для данного сценария. Такие реализации имеют жестко закодированную логику. Заглушка — разновидность тестового двойника (как и фиктивные объекты, макеты, шпионы и так далее). В Mockito чаще всего используются два типа тестовых двойников — макеты (mocks) и шпионы (spies).
Макеты (моки)
Такое тестирование называют мокингом. Создаются объекты-имитаторы, которые реализуют поведение реальной подсистемы. Моки используются как замена зависимостей.
С помощью Mockito разработчик создает имитатор — мок, указывает библиотеке, что делать при вызове определенных методов, а затем использует экземпляр имитатора в своем тесте вместо реального объекта. По умолчанию Mockito предоставляет реализацию для каждого метода mock. После тестирования можно запросить mock, чтобы узнать, какие конкретные методы были вызваны, или проверить побочные эффекты в виде изменения состояния.
Шпионы
Шпион — второй тестовый двойник, который создает Mockito. Для этого требуется экземпляр объекта, за которым можно наблюдать — шпионить. По умолчанию шпион делегирует все вызовы методов реальному объекту и записывает, какой метод был вызван и какие имел параметры.
Шпионы полезны для тестирования устаревшего кода. Но если приходится использовать шпион для частичного моделирования класса, значит, класс выполняет слишком много действий. Это идет вразрез с принципом единой ответственности.
Создание моков с помощью Mockito API
Библиотека Mockito позволяет создавать mock-объекты разными методами:
- с применением расширения @ExtendWith(MockitoExtension.class) для JUnit 5 в сочетании с аннотацией @Mock;
- помощью статического метода mock();
- использованием аннотации @Mock.
При использовании аннотации @Mock нужно подготовить к работе аннотированные поля. Расширение MockitoExtension делает это, вызывая статический метод MockitoAnnotations.initMocks().
Разберем использование методов на модели данных:
package com.example.junit5;
public class Database {
public boolean isAvailable() {
return false;
}
public int getUniqueId() {
return 45;
}
}
package com.example.junit5;
public class Service {
private Database database;
public Service(Database database) {
this.database = database;
}
public boolean query(String query) {
return database.isAvailable();
}
@Override
public String toString() {
return «Используется база данных с ID: » + String.valueOf(database.getUniqueId());
}
}
Модульный тест в Mockito для объекта Database может выглядеть так:
package com.example.junit5;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.when
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class ServiceTest {
@Mock
Database databaseMock;
@Test
public void testQuery() {
assertNotNull(databaseMock);
when(databaseMock.isAvailable()).thenReturn(true);
Service t = new Service(databaseMock);
boolean check = t.query(«* from t»);
assertTrue(check);
}
}
Код теста выполняет действия:
- дает Mockito указание создать макеты на основе аннотации @Mock — для этого требуется JUnit 5. Если используется другая версия JUnit, нужно вызвать Mock.init() в методе setup;
- сообщает Mockito, что нужно создать фиктивный экземпляр базы данных — databaseMock;
- настраивает Mock на возврат true при вызове его метода isAvailable;
- выполняет код тестируемого класса;
- утверждает, что вызов метода вернул true.
Настройка возвращаемых значений методов
Mockito API позволяет настраивать возвращаемые значения методов, которые вызываются для фиктивных объектов. Неопределенные вызовы методов возвращают пустые значения:
- null для объектов;
- 0 для чисел;
- false для логических значений;
- пустые коллекции для коллекций.
Использование when().thenReturn() и when().thenThrow()
Моки могут возвращать различные значения в зависимости от аргументов, переданных в метод. Цепочка методов when(…).thenReturn(…) используется для указания возвращаемого значения для вызова метода с заранее заданными параметрами. Для возврата значений также можно использовать такие методы, как anyString или anyInt.
Создание фиктивных финальных классов и статических методов
После того как библиотека mockito-inline пришла на смену mockito-core, у пользователей появилась возможность создавать моки финальных классов и статических методов. Предположим, в приложении есть такой финальный класс:
final class FinalClass {
public final String finalMethod() { return «строка текста»; }
}
С помощью приведенного ниже кода можно создать мок этого класса:
package com.example.junit5;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class MockitoMockFinal {
@Test
public void testMockFinal(@Mock FinalClass finalMocked) {
assertNotNull(finalMocked);
}
@Test
public void testMockFinalViaMockStatic() {
MockedStatic<FinalClass> mockStatic = Mockito.mockStatic(FinalClass.class);
assertNotNull(mockStatic);
}
}
Mockito делает код тестов проще и понятнее благодаря использованию фиктивных интерфейсов, прослушивающих вызовов, сопоставителей и захватчиков аргументов. Но, как и любой другой мощный инструмент, он должен использоваться правильно, чтобы быть максимально полезным.
0 комментариев