Чем отличаются mock и spy в Mockito и как их использовать
Что проверяют
Понимаете ли вы, что mock подменяет объект целиком, а spy исполняет настоящий код, и умеете ли выбрать между ними осознанно. Вопрос быстро уходит вглубь: проверяют, знаете ли вы, что шпион стаббится иначе и работает не с тем экземпляром, который вы передали.
Как отвечать
mock(Foo.class) создаёт заглушку. Настоящий код класса не исполняется вообще, все методы возвращают пустые значения (null, 0, false, пустая коллекция), а нужное поведение вы дописываете через when(...).thenReturn(...).
spy(obj) создаёт частичную заглушку: по умолчанию вызываются реальные методы, и вы подменяете только те, что мешают тесту.
Дальше назовите две детали, ради которых вопрос и задают.
Первая: Mockito не оборачивает переданный экземпляр, а создаёт свой объект и копирует в него поля. Исходная переменная про изменения через шпиона не знает.
List<String> real = new ArrayList<>();
List<String> spy = Mockito.spy(real);
spy.add("a");
// spy.size() == 1, real.size() == 0Вторая: стаббинг шпиона через when(spy.get(0)).thenReturn("x") сначала реально вызовет get(0) со всеми побочными эффектами. Безопасная форма: doReturn("x").when(spy).get(0).
Закройте выбором инструмента: mock это режим по умолчанию для зависимостей тестируемого класса, spy уместен только там, где класс достался легаси и его нельзя разрезать. Постоянная нужда в шпионе это сигнал, что класс делает слишком много.
Где спотыкаются
Считают шпиона обёрткой над оригиналом и пишут проверку по исходной переменной. Тест падает на ровном месте, потому что оригинал остался пустым.
Второе: стаббят шпиона через when, и реальный метод успевает сходить в базу или бросить исключение до того, как заглушка установлена. Разницы между when и doReturn для шпиона не видят.
Третье: смешивают матчеры и обычные значения в одном вызове, например verify(m).save(any(), userId). Mockito требует либо всё матчерами, либо ничем, иначе получите невнятную ошибку про число матчеров.