Пустое тело исключения в тесте Spring MVC

У меня возникли проблемы при попытке заставить MockMvc включить сообщение об исключении в тело ответа. У меня есть контроллер следующим образом:

@RequestMapping("/user/new")
public AbstractResponse create(@Valid NewUserParameters params, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) throw BadRequestException.of(bindingResult);
    // ...
}

где BadRequestException выглядит примерно так:

@ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = "bad request")
public class BadRequestException extends IllegalArgumentException {

    public BadRequestException(String cause) { super(cause); }

    public static BadRequestException of(BindingResult bindingResult) { /* ... */ }

}

И я запускаю следующий тест на контроллере /user/new:

@Test
public void testUserNew() throws Exception {
    getMockMvc().perform(post("/user/new")
            .param("username", username)
            .param("password", password))
            .andDo(print())
            .andExpect(status().isOk());
}

который печатает следующий вывод:

  Resolved Exception:
                Type = controller.exception.BadRequestException

        ModelAndView:
           View name = null
                View = null
               Model = null

            FlashMap:

MockHttpServletResponse:
              Status = 400
       Error message = bad request
             Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
        Content type = null
                Body = 
       Forwarded URL = null
      Redirected URL = null
             Cookies = []

Кто-нибудь знает, почему Body отсутствует в выводе print()?

Изменить: я не использую никаких пользовательских обработчиков исключений, и код работает, как и ожидалось, когда я запускаю сервер. То есть запуск приложения и выполнение того же запроса к серверу возвращает обратно

{"timestamp":1423076185822,
 "status":400,
 "error":"Bad Request",
 "exception":"controller.exception.BadRequestException",
 "message":"binding failed for field(s): password, username, username",
 "path":"/user/new"}

как и ожидалось. Следовательно, есть проблема с MockMvc, я полагаю. Он почему-то упускает возможность захватить поле message исключения, в то время как обработчик исключений по умолчанию обычного сервера приложений работает как положено.


person Volkan Yazıcı    schedule 04.02.2015    source источник


Ответы (3)


После открытия тикета мне сказали, что сообщение об ошибке в теле об этом позаботится Spring Boot, который настраивает сопоставления ошибок на уровне контейнера сервлетов, и, поскольку Spring MVC Test запускается с фиктивным запросом/ответом сервлета, такого сопоставления ошибок нет. Кроме того, они порекомендовали мне создать хотя бы один @WebIntegrationTest и придерживаться Spring MVC Test для моей логики контроллера.

В конце концов, я решил использовать свой собственный обработчик исключений и придерживаться MockMvc для остальных, как и раньше.

@ControllerAdvice
public class CustomExceptionHandler {

    @ExceptionHandler(Throwable.class)
    public @ResponseBody
    ExceptionResponse handle(HttpServletResponse response, Throwable throwable) {
        HttpStatus status = Optional
                .ofNullable(AnnotationUtils.getAnnotation(throwable.getClass(), ResponseStatus.class))
                .map(ResponseStatus::value)
                .orElse(HttpStatus.INTERNAL_SERVER_ERROR);
        response.setStatus(status.value());
        return new ExceptionResponse(throwable.getMessage());
    }

}

@Data
public class ExceptionResponse extends AbstractResponse {

    private final long timestamp = System.currentTimeMillis();

    private final String message;

    @JsonCreator
    public ExceptionResponse(String message) {
        checkNotNull(message, "message == NULL");
        this.message = message;
    }

}
person Volkan Yazıcı    schedule 10.02.2015

Вероятно, это означает, что вы либо не обработали исключение, либо действительно оставили тело пустым. Для обработки исключения либо добавьте обработчик ошибок в контроллер

@ExceptionHandler
public @ResponseBody String handle(BadRequestException e) {
    return "I'm the body";
}

или используйте глобальный обработчик ошибок, если вы используете версию 3.2 или выше.

@ControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler
    public @ResponseBody String handleBadRequestException(BadRequestException ex) {
        return "I'm the body";
    }
}

при этом тело будет заполнено, вы должны заполнить его своим сообщением об ошибке

person Master Slave    schedule 04.02.2015
comment
Я использую обработчик исключений по умолчанию. Проблема в том, что MockMvc не захватывает поле message, в то время как обработчик исключений по умолчанию обычного сервера приложений делает это. (См. мое редактирование вопроса.) - person Volkan Yazıcı; 04.02.2015

Обновленное решение:

Если вы не хотите выполнять полный интеграционный тест, но все же хотите убедиться, что сообщение соответствует ожиданиям, вы все равно можете сделать следующее:

String errorMessage = getMockMvc()
                        .perform(post("/user/new"))
                        ...
                        .andReturn().getResolvedException().getMessage();

assertThat(errorMessage, is("This is the error message!");
person snovelli    schedule 15.09.2020