Spring MVC 测试中的空异常主体
Empty Exception Body in Spring MVC Test
我在尝试让 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 = []
有人知道为什么 print()
输出中缺少 Body
吗?
编辑: 我没有使用任何自定义异常处理程序,当我 运行 服务器时代码按预期工作。也就是说,运行连接应用程序并向服务器发出相同的请求 returns 返回
{"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
字段,而常规应用程序服务器的默认异常处理程序按预期工作。
这可能意味着您没有处理异常,或者您确实将正文留空了。要处理异常,请在控制器中添加一个错误处理程序
@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";
}
}
这样正文就会被填充,你应该用你的错误信息填充它
在针对该问题打开 ticket 后,有人告诉我正文中的错误消息由 Spring Boot 处理,它在 Servlet 容器级别配置错误映射,因为 Spring MVC Test runs with a mock Servlet request/response, 没有这样的错误映射。此外,他们建议我至少创建一个 @WebIntegrationTest
并坚持对我的控制器逻辑进行 Spring MVC 测试。
最终,我决定使用我自己的自定义异常处理程序,并像以前一样坚持使用 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;
}
}
更新的解决方案:
如果您不想进行完整的集成测试,但仍想确保消息符合预期,您仍然可以执行以下操作:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");
我在尝试让 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 = []
有人知道为什么 print()
输出中缺少 Body
吗?
编辑: 我没有使用任何自定义异常处理程序,当我 运行 服务器时代码按预期工作。也就是说,运行连接应用程序并向服务器发出相同的请求 returns 返回
{"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
字段,而常规应用程序服务器的默认异常处理程序按预期工作。
这可能意味着您没有处理异常,或者您确实将正文留空了。要处理异常,请在控制器中添加一个错误处理程序
@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";
}
}
这样正文就会被填充,你应该用你的错误信息填充它
在针对该问题打开 ticket 后,有人告诉我正文中的错误消息由 Spring Boot 处理,它在 Servlet 容器级别配置错误映射,因为 Spring MVC Test runs with a mock Servlet request/response, 没有这样的错误映射。此外,他们建议我至少创建一个 @WebIntegrationTest
并坚持对我的控制器逻辑进行 Spring MVC 测试。
最终,我决定使用我自己的自定义异常处理程序,并像以前一样坚持使用 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;
}
}
更新的解决方案:
如果您不想进行完整的集成测试,但仍想确保消息符合预期,您仍然可以执行以下操作:
String errorMessage = getMockMvc()
.perform(post("/user/new"))
...
.andReturn().getResolvedException().getMessage();
assertThat(errorMessage, is("This is the error message!");