Разница между @Mock и @InjectMocks в Mockito: когда и что использовать?
Основное различие: @Mock создает заглушки, @InjectMocks внедряет их
Давайте разберем на пальцах. Аннотации @Mock и @InjectMocks решают разные задачи:
- @Mock — создает имитацию (mock) объекта. Вы указываете: "вот этот объект будет поддельным, я сам решу, что он должен возвращать".
- @InjectMocks — создает реальный объект тестируемого класса и автоматически внедряет в него все mock-объекты, помеченные @Mock (или @Spy).
В вашем примере всё верно:
@Mock
private UserRepository userRepository; // создаем подделку
@Mock
private EmailService emailService; // создаем подделку
@InjectMocks
private UserService userService; // создаем реальный UserService и вставляем в него подделки
Mockito смотрит на конструкторы UserService, находит тот, который принимает UserRepository и EmailService, и передает туда созданные mock-объекты. Вы получаете рабочий UserService, который внутри использует заглушки, а не реальную базу данных или почтовый сервер.
Что будет, если поменять местами?
Если вы напишете @Mock private UserService userService, то весь UserService станет заглушкой. Все его методы будут возвращать значения по умолчанию (null, 0, false), и вы не сможете протестировать реальную логику метода registerUser(). Тест потеряет смысл.
Если же написать @InjectMocks private UserRepository userRepository, то Mockito попытается создать реальный объект UserRepository и внедрить в него... ничего, потому что у UserRepository, скорее всего, нет зависимостей от UserService и EmailService. Это вызовет ошибку или создаст объект с null-полями.
Можно ли обойтись без @InjectMocks?
Да, можно создать объект вручную:
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService(userRepository, emailService);
}
Это абсолютно равнозначный подход. @InjectMocks — просто синтаксический сахар, который избавляет от написания такого кода. Но у него есть один нюанс: если у класса несколько конструкторов, Mockito выберет самый большой (с наибольшим количеством параметров). Если вам нужен другой конструктор — лучше создать объект вручную.
Почему @InjectMocks — это не всегда хорошо?
- Неявное поведение. Если вы добавите новую зависимость в конструктор UserService, тест может сломаться, потому что Mockito не сможет найти подходящий mock.
- Проблемы с полями. @InjectMocks также пытается внедрить зависимости через поля (если нет подходящего конструктора). Это может привести к неожиданностям, если у класса есть private-поля без сеттеров.
- Сложность отладки. При ручном создании объекта вы точно знаете, какие зависимости передаются. При @InjectMocks — полагаетесь на "магию" Mockito.
Рекомендация
Для простых случаев (один конструктор, все зависимости — mock-объекты) используйте @InjectMocks — это сокращает код. Если у вас сложная иерархия или несколько конструкторов — создавайте объект вручную через new. И никогда не ставьте @Mock на тестируемый класс — это делает тест бесполезным.
Альтернативный взгляд: используйте конструктор без @InjectMocks
Лично я предпочитаю не использовать @InjectMocks в продакшн-коде. Вот почему:
- Явность лучше неявности. Когда я пишу
new UserService(userRepoMock, emailMock), я сразу вижу, какие зависимости передаются. Если тест падает, я точно знаю, что передал. - Контроль над конструктором. @InjectMocks может выбрать неправильный конструктор, если их несколько. При ручном создании вы явно указываете, какой конструктор вызывать.
- Лучшая поддержка рефакторинга. Если вы переименуете параметр в конструкторе, @InjectMocks может сломаться (он ищет совпадение по типу, а не по имени). Ручное создание этого недостатка лишено.
Пример предпочтительного подхода:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@Mock
private EmailService emailService;
private UserService userService;
@BeforeEach
void setUp() {
userService = new UserService(userRepository, emailService);
}
// тесты...
}
Этот код чуть длиннее, но он более прозрачный и устойчивый к изменениям. @InjectMocks хорош для быстрых прототипов и обучения, но для серьезных проектов я рекомендую ручное связывание.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги