Другое

Mockito @Mock vs @InjectMocks: ключевые различия

Узнайте основные различия между аннотациями @Mock и @InjectMocks в Mockito. Поймите, как они работают вместе для тестирования и когда использовать для случая.

В чем разница между аннотациями @Mock и @InjectMocks в фреймворке тестирования Mockito? Как работают эти аннотации и когда следует использовать каждую из них в сценариях юнит‑тестирования?

Аннотация @Mock создаёт моки классов или интерфейсов для целей тестирования, тогда как @InjectMocks создаёт экземпляр тестируемого класса и автоматически внедряет в него смокированные зависимости (аннотированные @Mock). Эти аннотации работают совместно, позволяя изолировать юнит‑тесты, заменяя реальные зависимости управляемыми мок‑объектами. @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 пытается внедрить зависимости, используя три подхода в указанном порядке:

  1. Внедрение через конструктор: Если у тестируемого класса есть конструктор, Mockito пытается внедрить зависимости, используя самый большой конструктор (с наибольшим количеством параметров). Mockito сопоставляет мок‑объекты с параметрами конструктора по их типам.

  2. Внедрение через сеттеры: Если внедрение через конструктор не удалось, Mockito ищет методы‑сеттеры в классе. Он пытается найти методы, начинающиеся с «set», и сопоставить типы мок‑объектов.

  3. Внедрение через поля: Если и конструктор, и сеттеры не подходят, Mockito переходит к внедрению через поля. Он напрямую внедряет мок‑объекты в поля класса, при условии отсутствия ограничений доступа.

Процесс инициализации

Аннотации требуют правильной инициализации, чтобы работать корректно. Как показано в официальной документации Mockito, необходимо:

  • Использовать MockitoJUnitRunner как тестовый раннер, или
  • Вызвать MockitoAnnotations.initMocks(this) в методе @Before

Эта процедура гарантирует, что Mockito обработает все аннотации и создаст соответствующие мок‑объекты и внедрит их там, где это необходимо.

java
@RunWith(MockitoJUnitRunner.class)
public class ArticleManagerTest {
    @Mock
    private ArticleCalculator calculator;
    
    @InjectMocks
    private ArticleManager manager;
    
    @Test
    public void shouldDoSomething() {
        // test logic
    }
}

Практические примеры использования

Давайте рассмотрим конкретные примеры, демонстрирующие, как @Mock и @InjectMocks работают вместе в реальных сценариях тестирования.

Базовый пример

Из GeeksforGeeks приведён простой пример:

java
@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, вот более сложный сценарий:

java
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:

java
@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 экземпляров в ваш целевой объект.

Диаграмма принятия решения

Простой график выбора между аннотациями:

  1. Нужно ли создать тестовый двойник для зависимости? → Используйте @Mock
  2. Нужно ли протестировать класс, зависящий от других компонентов? → Используйте @InjectMocks для этого класса
  3. Нужно ли и моки, и целевой класс? → Используйте обе аннотации вместе

Лучшие практики и соображения

Хотя @Mock и @InjectMocks являются мощными инструментами, важно учитывать несколько аспектов для эффективного тестирования.

Преимущества использования аннотаций

  • Уменьшение шаблонного кода: Аннотации минимизируют повторяющийся код создания моков
  • Улучшение читаемости: Тесты становятся более понятными и самодокументируемыми
  • Автоматическое управление зависимостями: Mockito обрабатывает сложный процесс подключения

Возможные ограничения

Как отмечает Ted Vinke, @InjectMocks может вводить сложность и связность. Некоторые разработчики предпочитают ручную инициализацию для лучшего контроля:

java
@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:

java
@Mock(name = "mock")
private SomeBean someBean;

@Set(field = "someBean", source = "mock")
private UseBean useBean = new UseBean();

Краткое резюме лучших практик

  1. Используйте @Mock для создания мок‑зависимостей
  2. Используйте @InjectMocks для классов под тестом, которым нужны эти зависимости
  3. Комбинируйте обе аннотации для комплексного тестирования
  4. Рассмотрите ручную инициализацию для сложных случаев
  5. Всегда инициализируйте аннотации с помощью правильной настройки Mockito
  6. Используйте осмысленные имена для мок‑объектов, чтобы улучшить читаемость тестов

Заключение

Аннотации @Mock и @InjectMocks в Mockito выполняют взаимодополняющие роли в юнит‑тестировании. @Mock создаёт тестовые двойники, которые выступают в роли контролируемых зависимостей, а @InjectMocks создаёт экземпляр тестируемого класса и автоматически внедряет в него смокированные зависимости.

Ключевые выводы:

  • @Mock используется для создания мок‑зависимостей
  • @InjectMocks используется для самого класса, который нужно протестировать
  • Эти аннотации работают совместно, чтобы обеспечить изолированное тестирование
  • Mockito использует трёхступенчатый процесс внедрения: конструктор, сеттер, поле
  • Несмотря на мощность, в сложных сценариях стоит рассмотреть ручную инициализацию

Для эффективного юнит‑тестирования используйте @Mock для создания контролируемых зависимостей и @InjectMocks для тестирования реального класса в изоляции. Такой подход приводит к более поддерживаемым, читаемым и надёжным тестовым наборам, которые точно отражают поведение вашего кода.

Авторы
Проверено модерацией
Модерация