I mock a WebClient response with response header MediaType.APPLICATION_JSON_VALUE, and statuscode 400. When I run the test with WebTestClient, the returned statuscode is wrong:

@SpringBootTest
@AutoConfigureMockMvc
@Import(WebfluxWebClientServletTest.TestController.class)
public class WebfluxWebClientServletTest {
    @Autowired
    private WebTestClient webTestClient;

    @Autowired
    private ObjectMapper mapper;

    @Test
    public void test() throws Exception {
        MockWebServer server = new MockWebServer();
        server.start(8090);

        MockResponse mock = new MockResponse();
        mock.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        mock.setResponseCode(400);
        mock.setBody(mapper.writeValueAsString(Map.of("junit", "test")));
        server.enqueue(mock);

        webTestClient.get().uri("/junit")
            .exchange()
            .expectStatus().isBadRequest()
            .expectHeader()
            .contentTypeCompatibleWith(MediaType.APPLICATION_JSON); //assertion fails
    }

    @RestController
    static class TestController {
        @Autowired
        private WebClient client;

        @GetMapping(value = "/junit", produces = MediaType.APPLICATION_JSON_VALUE)
        public Mono<Object> send() {
            return client.get().uri("localhost:8090/external")
                .retrieve()
                .bodyToMono(Object.class);
        }
    }
}

The test fails with:

2022-03-10 17:14:23,939 [][] ERROR [main] o.s.t.w.r.s.ExchangeResult              : Request details for assertion failure:

> GET /junit
> WebTestClient-Request-Id: [1]

No content

< 400 BAD_REQUEST Bad Request
< X-Request-ID: [OqBTNj]
< Content-Type: [text/plain;charset=UTF-8]
< Content-Length: [16]
< Cache-Control: [no-cache, no-store, max-age=0, must-revalidate]
< Pragma: [no-cache]
< Expires: [0]
< X-Content-Type-Options: [nosniff]
< X-Frame-Options: [DENY]
< X-XSS-Protection: [1 ; mode=block]
< Referrer-Policy: [no-referrer]

{"junit":"test"}


java.lang.AssertionError: Response header 'Content-Type'=[text/plain;charset=UTF-8] is not compatible with [application/json]

spring-boot-webtestclient.zip

Comment From: wilkinsona

It's not clear what you're trying to do here. You've mentioned WebFlux but you're using @AutoConfigureMockMvc. Perhaps you're trying to use the MVC-backed WebTestClient but that's not clear from what you've described. It also isn't clear which version of Spring Boot you're using.

If you would like us to spend some more time investigating, please spend some time providing a complete yet minimal sample that reproduces the problem. You can share it with us by pushing it to a separate repository on GitHub or by zipping it up and attaching it to this issue.

Comment From: membersound

I added a zip for reproducing to the issue.

Sidenote: you're correct, I accidentally added @AutoConfigureMockMvc, but @AutoConfigureWebTestClient causes the same failure.

Comment From: bclozel

When using WebClient, retrieve() should emit an error message for an 4xx response status. I guess the problem might be in your controller implementation?

Comment From: membersound

Well, my goal is to have some kind of "global interceptor" that forwards the external error (received through webclient) both in statuscode + body. I want each WebClient call in my application to automatically apply this interceptor.

So is using a @RestControllerAdvice the wrong approach for this?

Comment From: bclozel

You've created this issue to report a bug in Spring Boot - it doesn't seem that's the case.

The behavior you're describing here happens because the ResponseEntity returned by the exception handler doesn't declare any content type. Changing it to the following works:

    @ExceptionHandler(WebClientResponseException.class)
    public ResponseEntity<Object> webClientResponseHandler(WebClientResponseException e) {
        return ResponseEntity.status(e.getStatusCode()).contentType(MediaType.APPLICATION_JSON).body(e.getResponseBodyAsString());
    }

If you have questions, please use StackOverflow.

Comment From: membersound

Okay, I see that github issues are not for implementation discussions. I will move to SO.

Anyways, you're correct: changing the handler as follows resolves the problem:

return ResponseEntity.status(e.getStatusCode())
                .contentType(e.getHeaders().getContentType())
                .body(e.getResponseBodyAsString());