В чем разница между моками и спаями в тестировании
Что проверяют
Знаете ли вы Mockito глубже @Mock из шаблона: где подменяется весь объект, а где только его часть. Следом обычно просят объяснить, почему для спая пишут doReturn(...).when(...), а не when(...).thenReturn(...).
Как отвечать
- мок (
Mockito.mock(),@Mock): пустая оболочка по типу класса или интерфейса. Методы ничего не делают и возвращают значения по умолчанию:null,0,false, пустые коллекции,Optional.empty(). Поведение задаёте сами через стабы; - спай (
Mockito.spy(),@Spy): обёртка над реальным объектом. Методы выполняют настоящий код, пока вы не переопределили конкретный вызов. По сути это частичный мок; - общее: на обоих работают
verify(),ArgumentCaptor,times(),never(), то есть проверка взаимодействий доступна и там, и там.
Когда что брать. Мок нужен для внешних зависимостей тестируемого класса: репозиторий, HTTP-клиент, отправка писем. Спай уместен редко, например в легаси-классе, где нужно заглушить один тяжёлый метод и оставить остальную логику. Документация Mockito прямо называет частичные моки признаком того, что класс пора разделить.
Про стаббинг спая: when(spy.load()).thenReturn(x) сначала вызовет настоящий load(), и если он ходит в сеть или бросает исключение, тест упадёт ещё на подготовке. Запись doReturn(x).when(spy).load() реальный метод не трогает.
В Spring Boot для бинов в контексте теперь используют @MockitoBean и @MockitoSpyBean, а прежние @MockBean и @SpyBean объявлены устаревшими начиная с Boot 3.4.
Где спотыкаются
Путают терминологию. В классификации тестовых двойников Мессароша спай это заглушка, которая записывает вызовы, а в Mockito спай это частичный мок. Сильный ответ сам оговаривает, в каком смысле употреблено слово.
Не знают, что Mockito.spy(obj) работает с копией объекта. Изменения, сделанные потом в исходном экземпляре, спай не увидит, и наоборот.
Мокают всё подряд, включая объекты-значения и сам тестируемый класс. Тест превращается в пересказ реализации и ломается при любом рефакторинге, хотя поведение не изменилось.