2

I am trying to put some unit/integration tests on my code,

I've a typical rest application build with spring boot, I am trying to test the AdminController save method:

@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);
}

The method is straight forward and it check the duplicate through the database, as I making the User object's username property unique:

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

When I try to save two users with the same username, the server refuses with this out-of-the-box JSON output: (Http code: 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"
}

I am ok with this, as it appears spring boot default behaviour is to send errors to /error (which is handled by BasicErrorController) that handle the exception and return this pretty json output:

[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]

But when I try to test this using 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());
}

instead of getting 500 internal server error, I got the following exception from 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)

So, My question is:

How to enable make the dispatcher servlet to forward to /error and return the same error as if the application is running (not under test)?

while keeping use MockMvc standealone, and without writing code in the rest method to handle the exception and return code 500.

(I think this might be impossible, so Should I assert on the exception instead? Or should I use webAppContextSetup? What is the best practice here? - jHipster for example uses standalone MockMvc in all its tests)

Related: unit test Spring MissingServletRequestParameterException JSON response (but mine not fixed)

Community
  • 1
  • 1
Muhammad Hewedy
  • 29,102
  • 44
  • 127
  • 219

1 Answers1

1

During runtime spring registers a error page at the servlet container and forwards all not handled errors there.

The MockMvcServer does not fully support forwarding, and I think that is the reason why you see a different result in your tests.

When saving a user fails, because another one with the same name already exists, this is not an (unexpected) internal server error. Instead you should catch the exception in the controller and return a HTTP 400 (Bad Request). This tells the client, the server is ok, but something with the request is not. You can add a ExceptionHandler to create the response for different types of exceptions.

All HTTP 400-499 are client errors, so they will not be forwarded to the error page, which is intended.

If the exception is handled, you should also receive the correct Http status and response in the MockMvc test.

  • Ok, your point regarding it is 400 not 500 is 100% correct and I agree with you (Thanks). But What makes me confused is that, In case of Spring do the Bean validation using `@Valid` annotation, and if the bean is invalid, spring send to `/error` which is handled by `BasicErrorController` and result in `400` not only when I call the service using Postman, but also in my integration test! see https://github.com/MuhammadHewedy/match/blob/master/match-api/src/test/java/match/controllers/AdminControllerIntTest.java#L55 , this test case try to send invalid user object which is hadled by spring – Muhammad Hewedy Sep 19 '16 at 22:05
  • I have noted that, in case of `@Valid` spring returns 400 bad request with no body in case of testing. – Muhammad Hewedy Sep 20 '16 at 21:48
  • @MuhammadHewedy: do you have a solution for getting the exceptions on the tests? – gsmachado Jul 25 '17 at 19:51