【问题标题】:Testing Rest Controllers in Spring Boot using Standalone MockMvc使用独立的 MockMvc 在 Spring Boot 中测试 Rest Controller
【发布时间】:2017-01-23 13:05:55
【问题描述】:

我正在尝试对我的代码进行一些单元/集成测试,

我有一个典型的使用 spring boot 构建的 rest 应用程序,我正在尝试测试 AdminController save 方法:

@RequestMapping(method = RequestMethod.POST)
public ResponseEntity<Void> save(@RequestBody @Valid User user) {
    user.setRole(Role.ROLE_ADMIN);
    user.setPassword(passwordEncoder.encode(user.getPassword()));
    return super.save(user);
}

该方法很简单,它通过数据库检查重复项,因为我使 User 对象的 username 属性唯一:

@NotNull
@Column(updatable = false, unique = true)
private String username;

当我尝试使用相同的用户名保存两个用户时,服务器拒绝使用此out-of-the-box JSON 输出:(Http 代码:500)

{
  "timestamp": 1473942296273,
  "status": 500,
  "error": "Internal Server Error",
  "exception": "org.springframework.dao.DataIntegrityViolationException",
  "message": "could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement",
  "path": "/api/admins"
}

我对此没意见,因为看来 Spring Boot 默认行为是将错误发送到处理异常并返回此漂亮 json 输出的 /error(由 BasicErrorController 处理):

[dispatcherServlet]             : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement] with root cause
DispatcherServlet               : DispatcherServlet with name 'dispatcherServlet' processing POST request for [/error]
RequestMappingHandlerMapping    : Looking up handler method for path /error
RequestMappingHandlerMapping    : Returning handler method [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)]
DefaultListableBeanFactory      : Returning cached instance of singleton bean 'basicErrorController'
OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor
HttpEntityMethodProcessor       : Written [{timestamp=Thu Sep 15 16:09:07 AST 2016, status=500, error=Internal Server Error, exception=org.springframework.dao.DataIntegrityViolationException, message=could not execute statement; SQL [n/a]; constraint [uk_sb8bbouer5wak8vyiiy4pf2bx]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement, path=/api/admins}] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@1482cc8]

但是当我尝试使用 mockito (see full test class) 进行测试时:

@Transactional
@Test
public void testSaveDupValidUser() throws Exception {
    User user = new User();
    user.setUsername("admin");

    this.mockMvc.perform(post("/api/admins")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(user)))
    .andDo(print())
    .andExpect(status().isOk());

    this.mockMvc.perform(post("/api/admins")
            .contentType(TestUtil.APPLICATION_JSON_UTF8)
            .content(TestUtil.convertObjectToJsonBytes(user)))
    .andDo(print())
    .andExpect(status().isInternalServerError());
}

我没有收到 500 内部服务器错误,而是从 junit 收到以下异常:

org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["UK_SB8BBOUER5WAK8VYIIY4PF2BX_INDEX_3 ON PUBLIC.""user""(USERNAME) VALUES ('admin', 1)"; SQL statement:
insert into "user" (id, created_by, created_date, modified_by, modified_date, enabled, password, role, username) values (null, ?, ?, ?, ?, ?, ?, ?, ?) [23505-192]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:982)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:872)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:648)

所以,我的问题是:

如何使调度程序 servlet 转发到 /error 并返回与应用程序正在运行(未测试)相同的错误?

同时保持独立使用MockMvc,并且不用在rest方法中编写代码来处理异常并返回代码500。

(我认为这可能是不可能的,所以我应该断言异常吗?还是应该使用webAppContextSetup?这里的最佳做法是什么?-jHipster 例如在其所有测试中使用独立的 MockMvc)

相关:unit test Spring MissingServletRequestParameterException JSON response(但我的不固定)

【问题讨论】:

    标签: spring spring-mvc spring-boot mockito jhipster


    【解决方案1】:

    在运行时,sp​​ring 在 servlet 容器中注册一个错误页面,并在那里转发所有未处理的错误。

    MockMvcServer 不完全支持转发,我认为这就是您在测试中看到不同结果的原因。

    当保存用户失败时,因为已经存在另一个同名用户,这不是(意外的)内部服务器错误。相反,您应该在控制器中捕获异常并返回 HTTP 400(错误请求)。这告诉客户端,服务器是好的,但请求的东西不是。 您可以添加ExceptionHandler 来为不同类型的异常创建响应。

    所有 HTTP 400-499 都是客户端错误,因此它们不会被转发到预期的错误页面。

    如果处理了异常,你也应该在 MockMvc 测试中收到正确的 Http 状态和响应。

    【讨论】:

    • 好的,你的观点是 400 而不是 500 是 100% 正确的,我同意你的看法(谢谢)。但是让我感到困惑的是,在 Spring 的情况下,使用 @Valid 注释进行 Bean 验证,如果 bean 无效,则 spring 发送到由 BasicErrorController 处理的 /error 并导致 400 不仅当我使用 Postman 调用服务时,也在我的集成测试中!见github.com/MuhammadHewedy/match/blob/master/match-api/src/test/…,这个测试用例尝试发送由spring处理的无效用户对象
    • 我注意到,如果@Valid spring 在测试的情况下返回 400 没有正文的错误请求。
    • @MuhammadHewedy:你有解决测试异常的方法吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-01
    • 2021-02-02
    • 1970-01-01
    • 2018-02-25
    • 2019-08-03
    • 2016-11-05
    • 1970-01-01
    相关资源
    最近更新 更多