Testing Exception-Handling in Spring-MVC

Specifying Exception-Handlers for Controllers in Spring MVC

Spring offers the annotation @ExceptionHandler to handle exceptions thrown by controllers. The annotation can be added to methods of a specific controller, or to methods of a @Component-class, that is itself annotated with @ControllerAdvice. The latter defines global exception-handling, that will be carried out by the DispaterServlet for all controllers. The former specifies exception-handlers for a single controller-class.

This mechanism is documented in the Springframework Documentation and it is neatly summarized in the blog-article Exception Handling in Spring MVC. In this article, we will focus on testing the sepcified exception-handlers.

Testing Exception-Handlers with the @WebMvcTest-Slice

Spring-Boot offers the annotation @WebMvcTest for tests of the controller-layer of your application. For a test annotated with @WebMvcTest, Spring-Boot will:

  • Auto-configure Spring MVC, Jackson, Gson, Message converters etc.
  • Load relevant components (@Controller, @RestController, @JsonComponent etc.)
  • Configure MockMVC

All other beans configured in the app will be ignored. Hence, a @WebMvcTest fits perfectly for testing exception-handlers, which are part of the controller-layer. It enables us, to mock away the other layers of the application and concentrate on the part, that we want to test.

Consider the following controller, that defines a request-handling and an accompanying exception-handler, for an IllegalArgumentException, that may by thrown in the business-logic:

@Controller
public class ExampleController
{
  @Autowired
  ExampleService service;

  @RequestMapping("/")
  public String controller(
      @RequestParam(required = false) Integer answer,
      Model model)
  {
    Boolean outcome = answer == null ? null : service.checkAnswer(answer);
    model.addAttribute("answer", answer);
    model.addAttribute("outcome", outcome);
   return "view";
  }

  @ResponseStatus(HttpStatus.BAD_REQUEST)
  @ExceptionHandler(IllegalArgumentException.class)
  public ModelAndView illegalArgumentException(IllegalArgumentException e)
  {
    LOG.error("{}: {}", HttpStatus.BAD_REQUEST, e.getMessage());
    ModelAndView mav = new ModelAndView("400");
    mav.addObject("exception", e);
    return mav;
  }
}

The exception-handler resolves the exception as 400: Bad Request and renders the specialized error-view 400.

With the help of @WebMvcTest, we can easily mock away the actual implementation of the business-logic and concentrate on the code under test: our specialized exception-handler.

@WebMvcTest(ExampleController.class)
class ExceptionHandlingApplicationTests
{
  @MockBean  ExampleService service;
  @Autowired MockMvc mvc;

  @Test
  @Autowired
  void test400ForExceptionInBusinessLogic() throws Exception {
    when(service.checkAnswer(anyInt())).thenThrow(new IllegalArgumentException("FOO!"));

    mvc
      .perform(get(URI.create("http://FOO/?answer=1234")))
      .andExpect(status().isBadRequest());

    verify(service, times(1)).checkAnswer(anyInt());
  }
}

We preform a GET with the help of the provided MockMvc and check, that the status of the response fullfills our expectations, if we tell our mocked business-logic to throw the IllegalArgumentException, that is resolved by our exception-handler.

Leave a Reply

Your email address will not be published. Required fields are marked *