Spring MVC Controllers Unit Test not calling @ControllerAdvice
I have a set of Controllers in the application and a class annotated as @ControllerAdvice
which sets up certain data elements that are used in each of these controllers. I'm using Spring MVC 3.2
and have Junits for these controllers. When I run the Junit the control is not going to the ControllerAdvice
class wheres it works fine if I deploy the app in Tomcat
and submit a request through browser.
Any thoughts please?.
Solution 1:
After using the answer from @eugene-to and another similar one here I found limitations and raised an issue on Spring: https://jira.spring.io/browse/SPR-12751
As a result, Spring test introduced the ability to register @ControllerAdvice
classes in the builder in 4.2. If you are using Spring Boot then you will need 1.3.0 or later.
With this improvement, if you are using standalone setup then you can set one or more ControllerAdvice
instances like so:
mockMvc = MockMvcBuilders.standaloneSetup(yourController)
.setControllerAdvice(new YourControllerAdvice())
.build();
Note: the name setControllerAdvice()
may not make it immediately obvious but you can pass many instances to it, since it has a var-args signature.
Solution 2:
Suppose you have class MyControllerAdvice annotated with @ControllerAdvice that has methods annotated with @ExceptionHandler. For MockMvc you can easily add this class as exception resolver.
@Before
public void beforeTest() {
MockMvc mockMvc = standaloneSetup(myControllers)
.setHandlerExceptionResolvers(createExceptionResolver())
.build();
}
private ExceptionHandlerExceptionResolver createExceptionResolver() {
ExceptionHandlerExceptionResolver exceptionResolver = new ExceptionHandlerExceptionResolver() {
protected ServletInvocableHandlerMethod getExceptionHandlerMethod(HandlerMethod handlerMethod, Exception exception) {
Method method = new ExceptionHandlerMethodResolver(MyControllerAdvice.class).resolveMethod(exception);
return new ServletInvocableHandlerMethod(new MyControllerAdvice(), method);
}
};
exceptionResolver.afterPropertiesSet();
return exceptionResolver;
}
Solution 3:
I had similar problem when trying to test ExceptionHandler
annotated with @ControllerAdvice
. In my case I had to add @Configuration
file with @EnableWebMvc
annotation to @ContextConfiguration
on test class.
So my test looked like this:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration(classes = {
RestProcessingExceptionHandler.class,
TestConfiguration.class,
RestProcessingExceptionThrowingController.class })
public class TestRestProcessingExceptionHandler {
private MockMvc mockMvc;
@Autowired
WebApplicationContext wac;
@Before
public void setup() {
mockMvc = webAppContextSetup(wac).build();
}
@Configuration
// !!! this is very important - conf with this annotation
// must be included in @ContextConfiguration
@EnableWebMvc
public static class TestConfiguration { }
@Controller
@RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {
@RequestMapping(value = "/exception", method = GET)
public @ResponseBody String find() {
throw new RestProcessingException("global_error_test");
}
}
@Test
public void testHandleException() throws Exception {
mockMvc.perform(get("/tests/exception"))
.andExpect(new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
result.getResponse().getContentAsString().contains("global_error_test");
}
})
.andExpect(status().isBadRequest());
}
}
With @EnableWebMvc
configuration my test passed.
Solution 4:
This code is working for me:
public class MyGlobalExceptionHandlerTest {
private MockMvc mockMvc;
@Mock
HealthController healthController;
@BeforeTest
public void setUp() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(healthController)
.setControllerAdvice(new GlobalExceptionHandler())
.build();
}
@Test(groups = { "services" })
public void testGlobalExceptionHandlerError() throws Exception {
Mockito.when(healthController.health())]
.thenThrow(new RuntimeException("Unexpected Exception"));
mockMvc.perform(get("/health")).andExpect(status().is(500));
}
}