Как сделать mock сервиса, который поднимается только локально в Spring
Что проверяют
Отличаете ли вы подмену зависимости при локальном запуске от моков в тестах и умеете ли сделать так, чтобы заглушка гарантированно не доехала до прода. Следом обычно спрашивают, как включить её одной настройкой и как проверить с ней сценарии ошибок.
Как отвечать
Задача обычно такая: внешний сервис вроде платёжного шлюза локально недоступен, а приложение должно подниматься. Каркас:
- интерфейс и две реализации:
PaymentClient, настоящийHttpPaymentClientиFakePaymentClient. Код сервисов зависит только от интерфейса; - профиль: на заглушке
@Profile("local"), на настоящем клиенте@Profile("!local"). Профиль включают черезSPRING_PROFILES_ACTIVE=localили конфигурацию запуска в IDE, а настройки кладут вapplication-local.yml; - флаг вместо профиля:
@ConditionalOnProperty(name = "payments.fake", havingValue = "true"), когда заглушку хочется включать точечно, не меняя остальную конфигурацию.
Вариант сильнее: мок вне процесса. WireMock в Docker Compose отвечает заранее описанными стабами, а в локальном профиле payments.base-url указывает на него. Так работают настоящий HTTP-клиент, сериализация и таймауты, а в стабах легко описать ответ 500 и задержку. С Spring Boot 3.1 модуль spring-boot-docker-compose поднимает compose-файл при старте приложения, а Testcontainers можно подключить к локальному запуску через SpringApplication.from(App::main).with(...).
Тестовые моки это другая история: @MockitoBean из Spring Framework 6.2 подменяет бин в контексте теста, а @MockBean в Spring Boot 3.4 объявлен устаревшим.
Где спотыкаются
Отвечают про @Mock и @InjectMocks в юнит-тесте, хотя вопрос про запуск приложения. Хуже того, смешивают @Mock с @SpringBootTest: такой мок в контекст не попадает, и бин продолжает ходить в настоящий сервис.
Кладут заглушку в основной код без условия или ставят @Profile("!prod"). Стоит забыть профиль на стенде, и там включается мок.
Делают заглушку, которая всегда отвечает успехом. Обработка отказов и таймаутов локально не проверяется ни разу.