Register @ControllerAdvice annotated Controller in JUnitTest with MockMVC

后端 未结 5 2019
孤街浪徒
孤街浪徒 2021-01-01 10:10

My @ControllerAdvice annotated Controller looks like this:

@ControllerAdvice
public class GlobalControllerExceptionHandler {

    @ResponseStatu         


        
相关标签:
5条回答
  • 2021-01-01 10:54

    If you have multiple advice classes, each with @ExceptionHandler and one of those classes is handling a very generic base exception, like @ExceptionHandler({Exception.class}), then you will need to add some priority ordering to your advice classes per this SO answer

    https://stackoverflow.com/a/19500823/378151

    0 讨论(0)
  • 2021-01-01 11:03

    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();
    
    0 讨论(0)
  • 2021-01-01 11:10

    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();
        }
    
    0 讨论(0)
  • 2021-01-01 11:10

    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();
    }
    
    0 讨论(0)
  • 2021-01-01 11:13

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

    MockMvcBuilders
         .standaloneSetup(myController)
         .setControllerAdvice(new MyontrollerAdvice())
         .build();
    
    0 讨论(0)
提交回复
热议问题