0
votes

How can the status code for a response provided by a @ExceptionHandler be set when the @Controller returns a reactive type (Mono)?

It seems that it is not possible via returning a ResponseEntity or annotating the @ExceptionHandler method with @ResponseStatus.

A fairly minimal test showing the issue (note that the response body and content type are correctly verified while the status code is OK when it should be INTERNAL_SERVER_ERROR):

class SpringWebMvcWithReactiveResponseTypeExceptionHandlerCheckTest {

    @RestController
    @RequestMapping("/error-check", produces = ["text/plain;charset=UTF-8"])
    class ExceptionHandlerCheckController {

        @GetMapping("errorMono")
        fun getErrorMono(): Mono<String> {
            return Mono.error(Exception())
        }
    }

    @ControllerAdvice
    class ErrorHandler : ResponseEntityExceptionHandler() {

        @ExceptionHandler(Exception::class)
        @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
        fun handleException(ex: Exception): ResponseEntity<*> = ResponseEntity
            .status(HttpStatus.INTERNAL_SERVER_ERROR)
            .contentType(MediaType.APPLICATION_PROBLEM_JSON)
            .body(mapOf("key" to "value"))
    }

    val mockMvc = MockMvcBuilders
        .standaloneSetup(ExceptionHandlerCheckController())
        .setControllerAdvice(ErrorHandler())
        .build()

    @Test
    fun `getErrorMono returns HTTP Status OK instead of the one set by an ExceptionHandler`() {
        mockMvc.get("/error-check/errorMono")
//            .andExpect { status { isInternalServerError() } }
            .andExpect { status { isOk() } }
            .asyncDispatch()
            .andExpect {
                content {
                    contentType(MediaType.APPLICATION_PROBLEM_JSON_VALUE)
                    json("""
                    {
                      "key": "value"
                    }
                    """,strict = true
                    )
                }
            }
    }
}

(build.gradle.kts excerpt showing relevant dependencies):

plugins: {
  id("org.springframework.boot") version "2.4.5"
  id("io.spring.dependency-management") version "1.0.11.RELEASE"
}

dependencies {
  implementation("org.springframework.boot:spring-boot-starter-web")
  implementation("org.springframework.boot:spring-boot-starter-webflux")
  testImplementation("org.springframework.boot:spring-boot-starter-test") 
}
1

1 Answers

0
votes

The issue is that .andExpect { status ... has to be called after .asyncDispatch() (as was done with content type and body). It seems that the http status is updated. Possibly this is actually part of http standard for async requests, but I suspect this is a bug of the underlying MockHttpServletResponse.