You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Mockito测试基于HttpStatusCode的WebClient exchangeToMono逻辑?

Testing WebClient's exchangeToMono Logic for NOT_FOUND Status with Mockito

I have this WebClient request code that handles HttpStatus.NOT_FOUND by returning an empty Optional, and wraps successful responses in an Optional<ByteArrayResource>:

webClient.get()
         .uri(uri)
         .accept(MediaType.APPLICATION_OCTET_STREAM)
         .<Optional<ByteArrayResource>>exchangeToMono(response -> {
             if (response.statusCode().equals(HttpStatus.NOT_FOUND)) {
                 return Mono.just(Optional.empty());
             }
             return response.bodyToMono(ByteArrayResource.class).map(Optional::of);
         })
         .block();

I'm trying to test the logic inside exchangeToMono() using Mockito, but my current approach only covers the successful scenario:

given(headersSpecHeadOpMock.exchangeToMono()).willReturn(Mono.just(clientResponse));

I can't figure out how to cover the HttpStatus.NOT_FOUND case. How do I adjust my tests to handle both scenarios?


To cover both the NOT_FOUND and successful response scenarios, you need to construct different ClientResponse instances with specific status codes instead of relying on a single mocked clientResponse. Here's a step-by-step solution:

1. Use Spring's ClientResponse Builder for Test Responses

ClientResponse is an interface, but Spring provides a builder via ClientResponse.create() that lets you easily generate test-friendly responses with custom status codes, bodies, and headers. This is far more flexible than mocking a generic ClientResponse.

2. Write Separate Test Cases for Each Scenario

Split your tests into two distinct cases to validate each code path in your exchangeToMono lambda.

Test Case 1: Validate NOT_FOUND Returns Empty Optional

@Test
void whenResponseIsNotFound_thenReturnEmptyOptional() {
    // 1. Build a NOT_FOUND response
    ClientResponse notFoundResponse = ClientResponse.create(HttpStatus.NOT_FOUND)
            .build();

    // 2. Mock the full WebClient call chain
    WebClient.RequestHeadersUriSpec<?> uriSpecMock = mock(WebClient.RequestHeadersUriSpec.class);
    WebClient.RequestHeadersSpec<?> headersSpecMock = mock(WebClient.RequestHeadersSpec.class);
    
    given(webClient.get()).willReturn(uriSpecMock);
    given(uriSpecMock.uri(anyString())).willReturn(headersSpecMock);
    given(headersSpecMock.accept(MediaType.APPLICATION_OCTET_STREAM)).willReturn(headersSpecMock);
    
    // 3. Pass our NOT_FOUND response to the exchangeToMono lambda
    given(headersSpecMock.exchangeToMono(any(Function.class)))
            .willAnswer(invocation -> {
                // Extract the lambda function passed to exchangeToMono
                Function<ClientResponse, Mono<Optional<ByteArrayResource>>> responseHandler = invocation.getArgument(0);
                // Apply our mocked NOT_FOUND response to trigger the empty Optional logic
                return responseHandler.apply(notFoundResponse);
            });

    // 4. Execute and verify the result
    Optional<ByteArrayResource> result = yourService.yourWebClientMethod();
    assertTrue(result.isEmpty());
}

Test Case 2: Validate Successful Response Returns Wrapped Body

@Test
void whenResponseIsSuccessful_thenReturnOptionalWithBody() {
    // 1. Prepare test data and build a successful response
    byte[] testContent = "sample file content".getBytes();
    ByteArrayResource expectedResource = new ByteArrayResource(testContent);
    
    ClientResponse successResponse = ClientResponse.create(HttpStatus.OK)
            .body(testContent)
            .build();

    // 2. Mock the WebClient chain (same as the first test)
    WebClient.RequestHeadersUriSpec<?> uriSpecMock = mock(WebClient.RequestHeadersUriSpec.class);
    WebClient.RequestHeadersSpec<?> headersSpecMock = mock(WebClient.RequestHeadersSpec.class);
    
    given(webClient.get()).willReturn(uriSpecMock);
    given(uriSpecMock.uri(anyString())).willReturn(headersSpecMock);
    given(headersSpecMock.accept(MediaType.APPLICATION_OCTET_STREAM)).willReturn(headersSpecMock);
    
    // 3. Pass our successful response to the exchangeToMono lambda
    given(headersSpecMock.exchangeToMono(any(Function.class)))
            .willAnswer(invocation -> {
                Function<ClientResponse, Mono<Optional<ByteArrayResource>>> responseHandler = invocation.getArgument(0);
                return responseHandler.apply(successResponse);
            });

    // 4. Execute and verify the result
    Optional<ByteArrayResource> result = yourService.yourWebClientMethod();
    assertTrue(result.isPresent());
    assertEquals(expectedResource.contentLength(), result.get().contentLength());
}

Key Takeaways

  • Use willAnswer() instead of willReturn(): This lets you pass your custom ClientResponse directly into the lambda logic in your production code, ensuring you test the actual decision-making flow.
  • Mock the full WebClient chain: Don't skip mocking any step of get() -> uri() -> accept() -> exchangeToMono() to avoid NullPointerExceptions.
  • Leverage Spring's test utilities: The ClientResponse builder simplifies creating realistic test responses without needing complex mocks.

内容的提问来源于stack exchange,提问作者Gustavo Oliveira

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 20:02:33