Зарегистрируйте аннотированный контроллер @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. Как это сделать?


person Rudolf Schmidt    schedule 01.09.2014    source источник
comment
Пробовали ли вы вариант MockMvcBuilders.webAppContextSetup, как описано в docs.spring.io/spring-framework/docs/current/ вместо MockMvcBuilders.standaloneSetup?   -  person geoand    schedule 01.09.2014


Ответы (5)


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

MockMvcBuilders
     .standaloneSetup(myController)
     .setControllerAdvice(new MyontrollerAdvice())
     .build();
person Morten Berg    schedule 16.11.2016
comment
Хорошо, намного проще, чем предыдущие решения. - person Paŭlo Ebermann; 09.06.2017

Чтобы активировать полную конфигурацию Spring MVC, вам нужно использовать 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 вы должны добавить bean-компонент Spring для AuthenticationService, который является макетом.

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

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


ОБНОВЛЕНИЕ

Немного покопавшись, я обнаружил, что 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's internal caches
        exceptionHandlerExceptionResolver.afterPropertiesSet();

        mockMvc = MockMvcBuilders.standaloneSetup(controller).setHandlerExceptionResolvers(exceptionHandlerExceptionResolver).build();
    }
person geoand    schedule 01.09.2014
comment
Спасибо за ваш пост, очень помог. Но есть ли другое более простое решение, чем webApplicationContext? Я попытался напрямую аннотировать класс AuthenticationException, и он работает с автономным сервером. Проблема в том, что я хочу отделить остальное от сервисного слоя и не смешивать оба слоя с аннотациями из другого слоя. Обработкой исключений следует управлять на оставшемся уровне. Есть ли другая возможность? - person Rudolf Schmidt; 01.09.2014
comment
@RudolfSchmidt Я рад, что это помогло тебе! Вы правы, что хотите разделить проблемы! Но я не понимаю, в чем проблема с webApplicationContext. Не могли бы вы немного рассказать об этом? Я не знаю другого решения, которое бы корректно регистрировало обработку исключений в тестовой среде. - person geoand; 01.09.2014
comment
@RudolfSchmidt Я нашел еще немного информации, которая может помочь. Будет обновлено в ближайшее время - person geoand; 01.09.2014
comment
@RudolfSchmidt Обновил ответ - person geoand; 01.09.2014
comment
К сожалению, это не работает, получение org.springframework.web.util.NestedServletException: Ошибка обработки запроса; вложенным исключением является AuthenticationException, нет проблем с использованием webApplicationContext, но он немного медленнее и требует больше кода (больше аннотаций). другое решение состоит в том, чтобы напрямую аннотировать класс исключений, но это не чисто. Надеюсь, есть другой обходной путь. - person Rudolf Schmidt; 01.09.2014
comment
@RudolfSchmidt Я обнаружил, что с приведенным выше решением @ExceptionHandler работает, если он находится в том же контроллере, а не в @ControllerAdvice. Я еще немного покопаюсь, чтобы посмотреть, что я могу придумать - person geoand; 01.09.2014
comment
@RudolfSchmidt На самом деле это отличное упражнение во внутренностях Spring! - person geoand; 01.09.2014
comment
@RudolfSchmidt Еще немного терпения :) Я очень близко - person geoand; 01.09.2014
comment
@RudolfSchmidt Посмотрите мое последнее обновление. Это должно работать для вас сейчас! - person geoand; 01.09.2014
comment
@JeeBee Приятно слышать! - person geoand; 17.06.2015

Прошло 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();
person JJZCorum    schedule 11.09.2014
comment
Спасибо за Ваш ответ! Основываясь на этом, я нашел решение для подхода RestAssuredMockMvc: stackoverflow.com/a/38435008/2239713 - person ilyailya; 26.07.2016

Если у вас есть несколько классов рекомендаций, каждый из которых имеет @ExceptionHandler, и один из этих классов обрабатывает очень общее базовое исключение, например @ExceptionHandler({Exception.class}), тогда вам нужно будет добавить некоторый порядок приоритета в ваши классы рекомендаций в соответствии с этим ответом SO.

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

person Snekse    schedule 14.08.2020

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

@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();
}
person Luis Carlos Fonseca Acuña    schedule 16.10.2015
comment
Мне кажется не имеет смысла использовать @Autowired в автономном режиме. У вас даже не должно быть контекста spring в автономном режиме. - person Bastian Voigt; 26.01.2016