2016-09-15 77 views
2

我试图把一些单元/集成测试我的代码,测试休息控制器在春季启动使用独立MockMvc

我一个典型的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); 
} 

的方法是直线前进,并通过数据库查询重复的,因为我使用户对象的属性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" 
} 

我确定这一点,因为它似乎春天开机默认行为是发送该处理异常错误/error(由BasicErrorController处理),并返回这个漂亮的JSON输出:

[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.springfr[email protected]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来转发到/错误并返回相同的错误,如果应用程序正在运行(不在测试中)?

同时保持使用MockMvc standealone,而不用在其余方法中编写代码来处理异常并返回代码500

(我认为这可能是不可能的,所以我应该断言异常上,而不是或者我应该使用webAppContextSetup什么是这里的最佳实践 - ???jHipster例如使用独立MockMvc在所有的测试)

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

回答

1

在运行期间,spring会在servlet容器中注册一个错误页面,并将所有未处理的错误转发到那里。

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

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

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

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

+0

好的,你的观点是400不是500是100%正确,我同意你(谢谢)。但是让我困惑的是,在Spring的情况下,使用'@ Valid'注释来执行Bean验证,如果bean无效,则发送到'/ error',由'BasicErrorController'处理并导致'400'不仅在我使用Postman调用服务时,而且在我的集成测试中!请参阅https://github.com/MuhammadHewedy/match/blob/master/match-api/src/test/java/match/controllers/AdminControllerIntTest.java#L55,此测试用例尝试发送无效的用户对象,该对象由弹簧 –

+0

我已经注意到,如果'@ Valid'弹簧在测试的情况下返回400个没有正文的错误请求。 –

+0

@MuhammadHewedy:你有解决方案来获取测试例外吗? – gsmachado