Подтвердить что ты не робот

Зарегистрировать @ControllerAdvice аннотированный контроллер в JUnitTest с помощью MockMVC

Мой @ControllerAdvice аннотированный контроллер выглядит следующим образом:

@ControllerAdvice
public class GlobalControllerExceptionHandler {

    @ResponseStatus(value = HttpStatus.UNAUTHORIZED)
    @ExceptionHandler(AuthenticationException.class)
    public void authenticationExceptionHandler() {
    }
}

Конечно, мое развитие связано с тестированием, и я хотел бы использовать обработчик исключений в тестах JUnit. Мой тестовый пример выглядит следующим образом:

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());
    }
}

Возможно, мне нужно зарегистрировать класс ControllerAdvice. Как это сделать?

4b9b3361

Ответ 1

Чтобы активировать полную конфигурацию MVC Spring, вам нужно использовать MockMvcBuilders.webAppContextSetup вместо MockMvcBuilders.standaloneSetup.

Подробнее о этой части документации Spring.

Ваш код будет выглядеть так:

@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());
    }
}

Затем внутри test-config.xml вы добавили Spring bean для AuthenticationService, который является макетом.

<bean id="authenticationService" class="org.mockito.Mockito" factory-method="mock">
    <constructor-arg value="your.package.structure.AuthenticationService"/>
</bean>

Разумеется, вы могли бы использовать профили для приведения mock AuthenticationService в тесте, если хотите повторно использовать свой обычный файл конфигурации Spring вместо создания test-config.xml.


UPDATE

После копания немного, я обнаружил, что StandaloneMockMvcBuilder, возвращенный (MockMvcBuilders.standaloneSetup), полностью настраивается. Это означает, что вы можете подключить любой разрешитель исключений, который вы предпочитаете.

Однако, поскольку вы используете @ControllerAdvice, код ниже не работает. Если, однако, ваш метод @ExceptionHandler находился внутри одного и того же контроллера, код, который вам нужно изменить, следующий:

mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(new ExceptionHandlerExceptionResolver()).build();

ОБНОВЛЕНИЕ 2

Несколько дополнительных копаний дали ответ на то, как вы можете зарегистрировать правильный обработчик исключений, когда вы также используете @ControllerAdvice.

Вам необходимо обновить установочный код в тесте до следующего:

    @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 internal caches
        exceptionHandlerExceptionResolver.afterPropertiesSet();

        mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(exceptionHandlerExceptionResolver).build();
    }

Ответ 2

Так как Spring 4.2, вы можете зарегистрировать свой ControllerAdvice непосредственно в своем StandaloneMockMvcBuilder:

MockMvcBuilders
     .standaloneSetup(myController)
     .setControllerAdvice(new MyontrollerAdvice())
     .build();

Ответ 3

Прошел исключение NestedServletException со следующим решением...

    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();

Ответ 4

Вы можете добавить это в свой тестовый класс

@Autowired
@Qualifier("handlerExceptionResolver")
void setExceptionResolver(HandlerExceptionResolver resolver)
{
    this.exceptionResolver = resolver;
}

а затем добавьте exceptionResolver к вашему MockMvc

@Before
public void setup() {
    MockitoAnnotations.initMocks(this);
    mockMvc = MockMvcBuilders.standaloneSetup(controller)
               .setHandlerExceptionResolvers(this.exceptionResolver).build();
}