Search code examples
spring-bootmockitomockmvcspring-restdocs

Re-use Spring Boot ErrorAttributes when using MockMvc and Spring REST Docs


Im writing tests for a controller that throws a custom exception (AuthenticationException in my case) which is annotated with @ResponseStatus(value = HttpStatus.BAD_REQUEST)

Calling the endpoint that throws the exception using i.e. curl works fine and i get the expected outcome with an example shown below:

{
  "timestamp": 1494185397677,
  "status": 400,
  "error": "Bad Request",
  "exception": "com.example.exception.AuthenticationException",
  "message": "These are not the credentials you are looking for",
  "path": "/status/throw/2"
}

When i write a test for this with Mockito which uses willThrow() i dont get any of the output that Spring Boot generates but just the response code as annotated in my exception class.

Here is my test:

@Test
public void throwsShouldShowResponseBody() throws Exception {
    given(this.service.getAccStatus())
            .willThrow(AuthenticationException.class);

    this.mvc.perform(get("/status/throw/2"))
            .andExpect(status().isBadRequest())
            .andDo(document("bad-credentials"));
}

Looking at similar questions it seems that this might be caused by MockMvc not following redirects which i think Spring Boot is using to push to /error but my ask is if there is anyway i can make this work so i dont have to write @ControllerAdvice class with something similar to ErrorAttributes that Spring Boot already provides. I dont wish to change the output that Spring Boot generates on an error.

Thanks -


Solution

  • As you've noted, it's a little bit tricky to document Spring Boot's error response when using MockMvc. This is because Spring Boot forwards the request to the error controller that's mapped to /error and MockMvc doesn't process forwards by default.

    One way to document an error response is to call /error directly with an appropriately configured request. There's an example of this in one of Spring REST Docs' samples:

    @Test
    public void errorExample() throws Exception {
        this.mockMvc
            .perform(get("/error")
                .requestAttr(RequestDispatcher.ERROR_STATUS_CODE, 400)
                .requestAttr(RequestDispatcher.ERROR_REQUEST_URI, "/notes")
                .requestAttr(RequestDispatcher.ERROR_MESSAGE, "The tag 'http://localhost:8080/tags/123' does not exist"))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("error", is("Bad Request")))
            .andExpect(jsonPath("timestamp", is(notNullValue())))
            .andExpect(jsonPath("status", is(400)))
            .andExpect(jsonPath("path", is(notNullValue())))
            .andDo(this.documentationHandler.document(
                responseFields(
                    fieldWithPath("error").description("The HTTP error that occurred, e.g. `Bad Request`"),
                    fieldWithPath("message").description("A description of the cause of the error"),
                    fieldWithPath("path").description("The path to which the request was made"),
                    fieldWithPath("status").description("The HTTP status code, e.g. `400`"),
                    fieldWithPath("timestamp").description("The time, in milliseconds, at which the error occurred"))));
    }
    

    It's then used in the resulting documentation to describe the error response format that's used across the entire API.