spring-boot: MockMvc doesn't use spring-boot's mvc exception handler
I’m trying to test my Controller using MockMvc.
The service used by Controller throws RuntimeException if something is wrong.
The spring-boot’s default exception resolver catches this exception and responds with status 500.
But when I use MockMvc in my test, the test method just throws the exception instead of catch status 500.
If I add explicit exception resolver using @ExceptionHandler
, then test method starts to work.
The simple example. My Controller just throws new RuntimeException()
for simplicity.
@RunWith(SpringRunner.class)
@WebMvcTest(MyController.class)
public class ControllerTests {
@Autowired
private MockMvc mvc;
@Test
public void testInternalServerError() throws Exception {
mvc.perform(get("/api/data"))
.andExpect(status().isInternalServerError());
}
}
Expected result: Test catches status 500 and passes.
Actual result: The exception is thrown and test fails.
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException
Maybe I’m wrong assuming that spring-boot installs some default mvc exception handler. But anyway the real application returns status 500 with properly formatted JSON of error details. So I think the MockMvc should do the same.
I can add the reproducible example if someone confirms that this is really not intended behaviour.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 27
- Comments: 17 (8 by maintainers)
Spring Boot’s error handling is based on Servlet container error mappings that result in an ERROR dispatch to an ErrorController. MockMvc however is container-less testing so with no Servlet container the exception simply bubbles up with nothing to stop it.
So MockMvc tests simply aren’t enough to test error responses generated through Spring Boot. I would argue that you shouldn’t be testing Spring Boot’s error handling. If you’re customizing it in any way you can write Spring Boot integration tests (with an actual container) to verify error responses. And then for MockMvc tests focus on fully testing the web layer while expecting exceptions to bubble up.
This is a typical unit vs integration tests trade off. You do unit tests even if they don’t test everything because they give you more control and run faster.
Thanks, @rstoyanchev.
@xak2000 Rossen’s already covered this, but I wanted to give you a direct answer. If you really want to test the precise format of the error response then you can use an integration test using
@SpringBootTest
configured with aDEFINED_PORT
orRANDOM_PORT
web environment andTestRestTemplate
.maybe this helps with a
MockMvc
approach:source: https://github.com/spring-projects/spring-framework/issues/17290#issuecomment-453422142
This only works if your
Exception
is annotated with@ResponseStatus
as, then, theResponseStatusExceptionResolver
handles the exception. If theException
is not annotated, then, it is thrown encapsulated inside aNestedServletException
.In that case you can test it with something like:
the problem with using
TestRestTemplate
is that you loose the ability to setup your test data in a@Transactional
integration test inside the test method, therefore, keeping the database clean for other tests to run.TestRestTemplate
causes the request to be processed in a different thread and therefore, you have to persistently modify the database which is a pain to clean up. I would be ok if the response is not the EXACT same forMockMvcTests
, but at least the HTTP status returned should be honoured.Unfortunately TestRestTemplate doesn’t work well with Spring REST Docs. 😞
And just to give visibility in terms of implementation, that’s how I’m doing: