Пустое тело исключения в весеннем тесте 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
поле исключения, тогда как обработчик исключений по умолчанию на обычном сервере приложений работает как положено.
2 ответа
После открытия заявки на решение этой проблемы мне сказали, что сообщение об ошибке в теле сообщения обрабатывается 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;
}
}
Вероятно, это означает, что вы либо не обработали исключение, либо действительно оставили тело пустым. Для обработки исключения либо добавьте обработчик ошибок в контроллер
@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";
}
}
при этом тело будет заполнено, вы должны заполнить его сообщением об ошибке
Обновленное решение:
Если вы не хотите проводить полный интеграционный тест, но все же хотите убедиться, что сообщение соответствует ожиданиям, вы все равно можете сделать следующее:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");