TGViewer
Эргономичный код Эргономичный код @ergonomic_code · 837 subscribers
Post #499 463

Forwarded from Spring АйО

🔗 Создание 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?

Ставьте 🔥 если считаете функциональность полезной.
  • ❤ 1
More from @ergonomic_code
  1. Sep 25, 2026photo post
  2. Sep 25, 2026Привет! Ну чтож. У меня наконец-то оглушительный успех с 100%-вайбкодингом. Знакомьтесь -…
  3. Sep 23, 2026Про творческий беспорядок в процессах Я куда ни приду лидом, меня первое время все ругают…
  4. Sep 23, 2026Саша пишет про процессы, как всегда очень круто, имхо
  5. Sep 4, 2026Post #717
  6. Aug 14, 2026Привет! Простые CRUD-приложения - это сложно Если помните, я уже писал, что с момента пере…
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 →