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