TGViewer
Spring АйО Spring АйО @spring_aio · 10.9K subscribers
Post #255 5.06K
🔗 Создание Deep Stubs в Mockito

При юнит-тестировании часто требуется проверить методы, которые используют цепочки вызовов, например, в WebClient из Spring WebFlux.

⚠️ Если задача позволяет, лучше воспользоваться MockWebServer или WireMock для гибкого тестирования HTTP-клиентов. Здесь WebClient выбран лишь из-за своей наглядности и популярности.

Представим клиент, запрашивающий случайную цитату у удалённого сервиса:


public class InspirationalQuotesClient {
private final WebClient webClient;

public InspirationalQuotesClient(WebClient webClient) {
this.webClient = webClient;
}

public String fetchRandomQuote() {
try {
return this.webClient
.get()
.uri("/api/quotes")
.retrieve()
.bodyToMono(String.class)
.block();
} catch (WebClientException e) {
return "Every time a mock returns, a mock a fairy dies.";
}
}
}


Для тестирования такого клиента стандартным способом нужно замокировать каждый вызов в цепочке, чтобы избежать NullPointerException. Это может сделать тест слишком громоздким:


@ExtendWith(MockitoExtension.class)
class InspirationalQuotesClientTest {

@Mock private WebClient webClient;
@InjectMocks private InspirationalQuotesClient cut;

@Test
void shouldReturnInMockingHell() {
WebClient.RequestHeadersUriSpec requestHeadersUriSpec = Mockito.mock(WebClient.RequestHeadersUriSpec.class);
WebClient.ResponseSpec responseSpec = Mockito.mock(WebClient.ResponseSpec.class);

when(webClient.get()).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.uri("/api/quotes")).thenReturn(requestHeadersUriSpec);
when(requestHeadersUriSpec.retrieve()).thenReturn(responseSpec);
when(responseSpec.bodyToMono(String.class)).thenReturn(Mono.just("We've escaped hell"));

String result = cut.fetchRandomQuote();
assertEquals("We've escaped hell", result);
}
}


Решение? Deep Stubs в Mockito. С ними можно упростить тест, описав всю цепочку вызовов сразу в конструкции when. Это значительно уменьшит количество кода:


@ExtendWith(MockitoExtension.class)
class DeepStubClientTest {

@Mock(answer = Answers.RETURNS_DEEP_STUBS)
private WebClient webClient;

@InjectMocks private InspirationalQuotesClient cut;

@Test
void shouldReturnQuoteFromRemoteSystem() {
Mockito.when(webClient.get().uri("/api/quotes").retrieve().bodyToMono(String.class))
.thenReturn(Mono.just("Less setup hell - but not better"));

String result = cut.fetchRandomQuote();
assertEquals("Less setup hell - but not better", result);
}
}


Используя параметр answer = Answers.RETURNS_DEEP_STUBS, мы возвращаем заглушку для каждого метода в цепочке. Но помните, что этот подход увеличивает зависимость теста от конкретной структуры вызовов, и любые изменения в цепочке потребуют изменений в тесте.

P.S. А как вы мокируете вызовы внешних API?

Ставьте 🔥 если считаете функциональность полезной.
  • 🔥 42
  • 👍 9
  • ❤ 7
  • ⚡ 4
  • 👎 4
  • 🤯 2
More from @spring_aio
  1. Oct 11, 2026🎬 Записи докладов с JPoint 2026 уже в открытом доступе JUG Ru Group выложили записи с про…
  2. Oct 9, 2026👩‍💻 Kotlin получил почти настоящие статики Начиная с Kotlin 2.5.0-Beta1 можно эксперимен…
  3. Oct 8, 2026🍃 Spring Boot ломает совместимость, G1 тормозит микросервисы, Java долго релизит фичи | S…
  4. Oct 6, 2026👩‍💻 Spring Config Import без магии У наших друзей из Axelix вышла статья о том, как Spri…
  5. Oct 5, 2026🥲 В Java 27 TLS научился переживать квантовую атаку При установке HTTPS-соединения клиент…
  6. Oct 3, 2026⚡️⚡️Строки кода стремятся подешеветь до цены токена. Что остается ценным? Про это — трек «…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →