Mockito @Mock vs @InjectMocks: ключевые различия
Узнайте основные различия между аннотациями @Mock и @InjectMocks в Mockito. Поймите, как они работают вместе для тестирования и когда использовать для случая.
В чем разница между аннотациями @Mock и @InjectMocks в фреймворке тестирования Mockito? Как работают эти аннотации и когда следует использовать каждую из них в сценариях юнит‑тестирования?
Аннотация @Mock создаёт моки классов или интерфейсов для целей тестирования, тогда как @InjectMocks создаёт экземпляр тестируемого класса и автоматически внедряет в него смокированные зависимости (аннотированные @Mock). Эти аннотации работают совместно, позволяя изолировать юнит‑тесты, заменяя реальные зависимости управляемыми мок‑объектами. @Mock следует использовать для создания мок‑зависимостей, а @InjectMocks – для самого класса, который нужно протестировать в изоляции.
Содержание
- Что такое аннотации @Mock и @InjectMocks?
- Как эти аннотации работают внутренне
- Практические примеры использования
- Когда использовать @Mock, а когда @InjectMocks
- Лучшие практики и соображения
Что такое аннотации @Mock и @InjectMocks?
Аннотации @Mock и @InjectMocks являются фундаментальными компонентами фреймворка Mockito, которые упрощают создание и управление мок‑объектами в юнит‑тестах.
Аннотация @Mock
Аннотация @Mock создаёт мок‑экземпляры классов или интерфейсов. Согласно статье на GeeksforGeeks, эта аннотация позволяет разработчикам создавать мок‑объекты, которые можно использовать в качестве тестовых двойников в юнит‑тестах. При использовании @Mock Mockito автоматически:
- Создаёт мок‑реализацию указанного класса или интерфейса
- Позволяет определить конкретное поведение с помощью методов
when().thenReturn() - Позволяет проверять взаимодействия с мок‑объектом с помощью
verify()
Аннотация @InjectMocks
Аннотация @InjectMocks выполняет другую роль. Как объясняет HowToDoInJava, @InjectMocks создаёт реальный экземпляр тестируемого класса и автоматически внедряет в него смокированные зависимости (аннотированные @Mock). Эта аннотация необходима для:
- Создания объекта, который нужно протестировать
- Автоматического подключения мок‑зависимостей
- Возможности тестировать класс в изоляции от его зависимостей
Ключевой вывод: @Mock создаёт тестовые двойники, а @InjectMocks создаёт реальный объект под тест и связывает моки с ним.
Как эти аннотации работают внутренне
Понимание внутреннего механизма этих аннотаций помогает писать более эффективные юнит‑тесты. Mockito использует сложный механизм внедрения, чтобы подключить мок‑объекты к тестируемому классу.
Трёхступенчатый процесс внедрения
Согласно руководству DigitalOcean, Mockito пытается внедрить зависимости, используя три подхода в указанном порядке:
-
Внедрение через конструктор: Если у тестируемого класса есть конструктор, Mockito пытается внедрить зависимости, используя самый большой конструктор (с наибольшим количеством параметров). Mockito сопоставляет мок‑объекты с параметрами конструктора по их типам.
-
Внедрение через сеттеры: Если внедрение через конструктор не удалось, Mockito ищет методы‑сеттеры в классе. Он пытается найти методы, начинающиеся с «set», и сопоставить типы мок‑объектов.
-
Внедрение через поля: Если и конструктор, и сеттеры не подходят, Mockito переходит к внедрению через поля. Он напрямую внедряет мок‑объекты в поля класса, при условии отсутствия ограничений доступа.
Процесс инициализации
Аннотации требуют правильной инициализации, чтобы работать корректно. Как показано в официальной документации Mockito, необходимо:
- Использовать
MockitoJUnitRunnerкак тестовый раннер, или - Вызвать
MockitoAnnotations.initMocks(this)в методе@Before
Эта процедура гарантирует, что Mockito обработает все аннотации и создаст соответствующие мок‑объекты и внедрит их там, где это необходимо.
@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@InjectMocks
private ArticleManager manager;
@Test
public void shouldDoSomething() {
// test logic
}
}
Практические примеры использования
Давайте рассмотрим конкретные примеры, демонстрирующие, как @Mock и @InjectMocks работают вместе в реальных сценариях тестирования.
Базовый пример
Из GeeksforGeeks приведён простой пример:
@RunWith(MockitoJUnitRunner.class)
class StudentTest {
@Mock
private Pen pen;
@InjectMocks
private Student student;
@Test
public void writeWithPenTest() throws Exception {
Mockito.when(pen.getRedPen()).thenReturn("Red Pen");
assertEquals("Student Write with: Red Pen", student.write());
}
}
В этом примере:
@Mock private Pen pen;создаёт мок‑объект Pen@InjectMocks private Student student;создаёт экземпляр Student и внедряет в него мок Pen- Тест проверяет, что класс Student корректно работает с мок‑зависимостью Pen
Сложный пример с зависимостями
Согласно HowToDoInJava, вот более сложный сценарий:
public class ArticleManagerTest {
@Mock
private ArticleCalculator calculator;
@Mock(name = "database")
private ArticleDatabase dbMock;
@Spy
private UserProvider userProvider = new ConsumerUserProvider();
@InjectMocks
private ArticleManager manager;
@Test
public void shouldDoSomething() {
manager.initiateArticle();
verify(dbMock).addListener(any(ArticleListener.class));
}
}
В этом примере показано:
- Несколько мок‑объектов разных типов
- Аннотация @Spy (создаёт частичный мок)
- @InjectMocks создаёт и внедряет все зависимости в ArticleManager
Пример уровня сервиса
Из Medium:
@Mock
private MyService myService; // Создаёт мок‑экземпляр MyService
@InjectMocks
private MyController myController; // Создаёт экземпляр и внедряет смокированные зависимости
@Test
public void testControllerMethod() {
when(myService.getData()).thenReturn("test data");
String result = myController.processData();
assertEquals("processed: test data", result);
}
Когда использовать @Mock, а когда @InjectMocks
Понимание того, когда использовать каждую аннотацию, критично для написания эффективных юнит‑тестов. Ниже приведены чёткие рекомендации:
Используйте @Mock, когда:
- Нужно создать мок‑объекты зависимостей
- Необходимо изолировать систему под тестом от реальных зависимостей
- Нужно управлять поведением внешних компонентов во время тестирования
- Нужно проверять взаимодействия с зависимостями
Согласно Stack Overflow, @Mock необходим для создания строительных блоков вашего тестового окружения.
Используйте @InjectMocks, когда:
- Нужно протестировать класс, который имеет зависимости
- Нужно автоматически подключить мок‑зависимости к тестируемому классу
- Тестируется сервис, контроллер или любой класс, зависящий от других компонентов
- Нужно избежать ручного кода внедрения зависимостей
Baeldung подчёркивает, что @InjectMocks необходим для внедрения как @Spy, так и @Mock экземпляров в ваш целевой объект.
Диаграмма принятия решения
Простой график выбора между аннотациями:
- Нужно ли создать тестовый двойник для зависимости? → Используйте @Mock
- Нужно ли протестировать класс, зависящий от других компонентов? → Используйте @InjectMocks для этого класса
- Нужно ли и моки, и целевой класс? → Используйте обе аннотации вместе
Лучшие практики и соображения
Хотя @Mock и @InjectMocks являются мощными инструментами, важно учитывать несколько аспектов для эффективного тестирования.
Преимущества использования аннотаций
- Уменьшение шаблонного кода: Аннотации минимизируют повторяющийся код создания моков
- Улучшение читаемости: Тесты становятся более понятными и самодокументируемыми
- Автоматическое управление зависимостями: Mockito обрабатывает сложный процесс подключения
Возможные ограничения
Как отмечает Ted Vinke, @InjectMocks может вводить сложность и связность. Некоторые разработчики предпочитают ручную инициализацию для лучшего контроля:
@RunWith(MockitoJUnitRunner.class)
public class PlannerServiceImplTest {
@Mock
private PlannerClient plannerClient;
@Mock
private AuditService auditService;
private PlannerServiceImpl plannerService;
@Before
void setUp() {
plannerService = new PlannerServiceImpl(plannerClient, auditService);
}
}
Альтернативный подход
Для более сложных сценариев рассмотрите предложенную аннотацию @Set, упомянутую в обсуждениях GitHub:
@Mock(name = "mock")
private SomeBean someBean;
@Set(field = "someBean", source = "mock")
private UseBean useBean = new UseBean();
Краткое резюме лучших практик
- Используйте @Mock для создания мок‑зависимостей
- Используйте @InjectMocks для классов под тестом, которым нужны эти зависимости
- Комбинируйте обе аннотации для комплексного тестирования
- Рассмотрите ручную инициализацию для сложных случаев
- Всегда инициализируйте аннотации с помощью правильной настройки Mockito
- Используйте осмысленные имена для мок‑объектов, чтобы улучшить читаемость тестов
Заключение
Аннотации @Mock и @InjectMocks в Mockito выполняют взаимодополняющие роли в юнит‑тестировании. @Mock создаёт тестовые двойники, которые выступают в роли контролируемых зависимостей, а @InjectMocks создаёт экземпляр тестируемого класса и автоматически внедряет в него смокированные зависимости.
Ключевые выводы:
- @Mock используется для создания мок‑зависимостей
- @InjectMocks используется для самого класса, который нужно протестировать
- Эти аннотации работают совместно, чтобы обеспечить изолированное тестирование
- Mockito использует трёхступенчатый процесс внедрения: конструктор, сеттер, поле
- Несмотря на мощность, в сложных сценариях стоит рассмотреть ручную инициализацию
Для эффективного юнит‑тестирования используйте @Mock для создания контролируемых зависимостей и @InjectMocks для тестирования реального класса в изоляции. Такой подход приводит к более поддерживаемым, читаемым и надёжным тестовым наборам, которые точно отражают поведение вашего кода.