Как мокать клиентов в тестах
Что проверяют
Умеете ли вы изолировать код от внешних систем и выбирать уровень подмены: мок интерфейса, фейковый HTTP-сервер или настоящий сервис в контейнере. Уточнение обычно такое: как проверить, что сервис правильно переживает таймаут или ответ 500 от клиента.
Как отвечать
Сначала архитектурное условие: клиент приходит в сервис через конструктор и, желательно, как интерфейс. Тогда подмена тривиальна. Дальше три уровня:
- Mockito в юнит-тесте:
@ExtendWith(MockitoExtension.class), поле с@Mock, сервис с@InjectMocks. Поведение задаётся черезwhen(client.getUser(42)).thenReturn(user)илиthenThrow(...), вызовы проверяются черезverifyиArgumentCaptor. В Mockito 5 по умолчанию inline mock maker, поэтому мокаются иfinal-классы; - Spring-контекст: в
@SpringBootTestили срезах вроде@WebMvcTestбин клиента подменяют аннотацией@MockitoBeanиз Spring Framework 6.2, а старый@MockBeanобъявлен устаревшим начиная со Spring Boot 3.4; - HTTP на уровне провода: для
RestClient,WebClientили Feign поднимают WireMock (дляRestTemplateиRestClientподойдёт иMockRestServiceServer) и проверяют реальную сериализацию, заголовки, ретраи, поведение на 500 и на таймауте. Мок интерфейса этого не покажет.
Для интеграционных тестов с базой, Kafka или Redis берут Testcontainers: там мок уже не нужен, поднимается настоящий сервис в Docker.
Правило выбора: юнит-тест проверяет логику сервиса, тест с WireMock проверяет, что клиент правильно говорит по HTTP. Нужны оба, и друг друга они не заменяют.
Где спотыкаются
Мокают всё подряд, включая DTO и собственные утилиты. Тест начинает проверять реализацию, а не поведение, и ломается при любом рефакторинге.
Ставят verify на каждый вызов. Проверять стоит побочные эффекты, важные для бизнеса, например что платёж отправлен ровно один раз, а не каждое обращение к геттеру.
Создают клиента внутри сервиса через new или статический метод, а потом мучаются с mockStatic. Проблема тут в дизайне класса, а не в библиотеке моков.