Testing thrown exception in controller

11,902

Solution 1

Our solution is rather a workaround: The exception is caught in advice and error body is returned as HTTP response. Here is how the mock works:

MockMvc mockMvc = MockMvcBuilders.standaloneSetup(controller)
                        .setHandlerExceptionResolvers(withExceptionControllerAdvice())
                        .build();

private ExceptionHandlerExceptionResolver withExceptionControllerAdvice() {
    final ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
        @Override
        protected ServletInvocableHandlerMethod getExceptionHandlerMethod(final HandlerMethod handlerMethod, final Exception exception) {
            Method method = new ExceptionHandlerMethodResolver(TestAdvice.class).resolveMethod(exception);
            if (method != null) {
                return new ServletInvocableHandlerMethod(new TestAdvice(), method);
            }
            return super.getExceptionHandlerMethod(handlerMethod, exception);
        }
    };
    exceptionResolver.afterPropertiesSet();
    return exceptionResolver;
}

Advice class:

@ControllerAdvice
public class TestAdvice {
    @ExceptionHandler(Exception.class)
    @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
    public Object exceptionHandler(Exception e) {
        return new HttpEntity<>(e.getMessage());
    }
}

After than, following test method passes successfully:

@Test
public void testException
    mockMvc.perform(post("/exception/path"))
        .andExpect(status().is5xxServerError())
        .andExpect(content().string("Exception body"));
}

Solution 2

Easier way is to inject @ExceptionHandler into your Spring Test Context or it throws exception right in MockMvc.perform() just before .andExpect().

@ContextConfiguration(classes = { My_ExceptionHandler_AreHere.class })
@AutoConfigureMockMvc
public class Test {
    @Autowired
    private MockMvc mvc;

    @Test
    public void test() {
        RequestBuilder requestBuilder = MockMvcRequestBuilders.post("/update")
                .param("branchId", "13000")
                .param("triggerId", "1");
        MvcResult mvcResult = mvc.perform(requestBuilder)
                .andExpect(MockMvcResultMatchers.status().is4xxClientError())
                .andExpect(MockMvcResultMatchers.content().contentType(MediaType.APPLICATION_JSON_UTF8))
                .andExpect(__ -> Assert.assertThat(
                        __.getResolvedException(),
                        CoreMatchers.instanceOf(SecurityException.class)))
                .andReturn();
}

That way MvcResult.getResolvedException() holds @Controller's exception!

https://stackoverflow.com/a/61016827/173149

Share:
11,902
mtyurt
Author by

mtyurt

Currently I'm just trying to learn.

Updated on June 04, 2022

Comments

  • mtyurt
    mtyurt almost 2 years

    I want to perform a test on a controller method which throws an exception. The method is something like this:

    @RequestMapping("/do")
    public ResponseEntity doIt(@RequestBody Request request) throws Exception {
        throw new NullPointerException();
    }
    

    When I try to test this method with following code part,

     mockMvc.perform(post("/do")
                    .contentType(MediaType.APPLICATION_JSON)
                    .content(JSON.toJson(request)))
    

    NestedServletException is thrown from Spring libraries. How can I test that NullPointerException is thrown instead of NestedServletException?

  • lkamal
    lkamal over 5 years
    What is controller in MockMvcBuilders.standaloneSetup(controller) line?
  • mtyurt
    mtyurt over 5 years
    The controller bean that is being tested, it is injected to the test class.