如何使用Mockito测试基于HttpStatusCode的WebClient exchangeToMono逻辑?
I have this WebClient request code that handles
HttpStatus.NOT_FOUNDby returning an emptyOptional, and wraps successful responses in anOptional<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_FOUNDcase. 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 ofwillReturn(): This lets you pass your customClientResponsedirectly 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 avoidNullPointerExceptions. - Leverage Spring's test utilities: The
ClientResponsebuilder simplifies creating realistic test responses without needing complex mocks.
内容的提问来源于stack exchange,提问作者Gustavo Oliveira

