Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Register @ControllerAdvice annotated Controller in JUnitTest with MockMVC

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?

like image 855
Rudolf Schmidt Avatar asked Sep 01 '14 10:09

Rudolf Schmidt


2 Answers

Since Spring 4.2, you can register your ControllerAdvice directly into your StandaloneMockMvcBuilder:

MockMvcBuilders
     .standaloneSetup(myController)
     .setControllerAdvice(new MyontrollerAdvice())
     .build();
like image 153
Morten Berg Avatar answered Oct 17 '22 05:10

Morten Berg


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();
    }
like image 27
geoand Avatar answered Oct 17 '22 06:10

geoand