My @ControllerAdvice
annotated Controller looks like this:
@ControllerAdvice
public class GlobalControllerExceptionHandler {
@ResponseStatus(value = HttpStatus.UNAUTHORIZED)
@ExceptionHandler(AuthenticationException.class)
public void authenticationExceptionHandler() {
}
}
Of course my development is test driven and I would like to use my exception Handler in the JUnit Tests. My Test case looks like this:
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
@InjectMocks
private ClientQueriesController controller;
@Mock
private AuthenticationService authenticationService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller).build();
}
@Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Probably I need to register the ControllerAdvice
Class. How to do that?
Since Spring 4.2, you can register your ControllerAdvice directly into your StandaloneMockMvcBuilder:
MockMvcBuilders
.standaloneSetup(myController)
.setControllerAdvice(new MyontrollerAdvice())
.build();
In order for the full Spring MVC configuration to get activated, you need to use MockMvcBuilders.webAppContextSetup
instead of MockMvcBuilders.standaloneSetup
.
Check out this part of the Spring documentation for more details.
Your code would look like:
@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@ContextConfiguration("test-config.xml")
public class ClientQueriesControllerTest {
private MockMvc mockMvc;
@Autowired
private WebApplicationContext webApplicationContext;
@Autowired
private AuthenticationService authenticationService;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
@Test
public void findAllAccountRelatedClientsUnauthorized() throws Exception {
when(authenticationService.validateAuthorization(anyString())).thenThrow(AuthenticationException.class);
mockMvc.perform(get("/rest/clients").header("Authorization", UUID.randomUUID().toString()))
.andExpect(status().isUnauthorized());
}
}
Then inside test-config.xml
you would add a Spring bean for AuthenticationService
that is a mock.
<bean id="authenticationService" class="org.mockito.Mockito" factory-method="mock">
<constructor-arg value="your.package.structure.AuthenticationService"/>
</bean>
You could of course use profiles to inject the mock AuthenticationService
in the tests if want to reuse your regular Spring configuration file instead of creating test-config.xml
.
UPDATE
After digging around a bit, I found that StandaloneMockMvcBuilder
returned by (MockMvcBuilders.standaloneSetup
) is totally customizable. That means that you can plug in whatever exception resolver you prefer.
However since you are using @ControllerAdvice
, the code below will not work.
If however your @ExceptionHandler
method was inside the same controller the code all you would have to change is the following:
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(new ExceptionHandlerExceptionResolver()).build();
UPDATE 2
Some more digging gave the answer to how you can register a correct exception handler when you are also using @ControllerAdvice
.
You need to update the setup code in the test to the following:
@Before
public void setUp() throws Exception {
final ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver = new ExceptionHandlerExceptionResolver();
//here we need to setup a dummy application context that only registers the GlobalControllerExceptionHandler
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerBeanDefinition("advice", new RootBeanDefinition(GlobalControllerExceptionHandler.class, null, null));
//set the application context of the resolver to the dummy application context we just created
exceptionHandlerExceptionResolver.setApplicationContext(applicationContext);
//needed in order to force the exception resolver to update it's internal caches
exceptionHandlerExceptionResolver.afterPropertiesSet();
mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(exceptionHandlerExceptionResolver).build();
}
Got past the NestedServletException with the following solution...
final StaticApplicationContext applicationContext = new StaticApplicationContext();
applicationContext.registerSingleton("exceptionHandler", GlobalControllerExceptionHandler.class);
final WebMvcConfigurationSupport webMvcConfigurationSupport = new WebMvcConfigurationSupport();
webMvcConfigurationSupport.setApplicationContext(applicationContext);
mockMvc = MockMvcBuilders.standaloneSetup(controller).
setHandlerExceptionResolvers(webMvcConfigurationSupport.handlerExceptionResolver()).
build();
You can add this to your test class
@Autowired
@Qualifier("handlerExceptionResolver")
void setExceptionResolver(HandlerExceptionResolver resolver)
{
this.exceptionResolver = resolver;
}
and then add the exceptionResolver
to your MockMvc
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setHandlerExceptionResolvers(this.exceptionResolver).build();
}