Переопределить @FeignClient с помощью bean-компонента @Configuration для тестов

Можно ли переопределить bean-компонент, созданный с помощью аннотации @FeignClient, просто создав bean-компонент @Configuration, который содержит его фиктивную версию для тестирования?

Я уже пробовал это, но кажется, что bean-компонент @FeignClient создается последним (по крайней мере, я так думаю), так как в моем тесте я всегда ввожу реальную версию, а не издевательскую. В том же конфигурационном файле у меня есть другой bean-компонент, созданный без каких-либо аннотаций (кроме @Component), который издевался таким же образом, просто используя имя настоящего, и он отлично работает.

Я пытался использовать @MockBean, чтобы издеваться над ним, и это работает, но у проекта есть некоторые причуды, из-за которых создание другого контекста Spring нарушает тесты.

Спасибо.

РЕДАКТИРОВАТЬ. На самом деле, я только что отладил тест и понял, что если я использую то же имя, что и клиент Feign, отладчик даже не остановится в bean-компоненте @Configuration для создания фиктивной версии. Изменение имени на что-то другое работает, но оно просто создает другой компонент того же типа с новым именем. Есть ли какая-либо конфигурация, которую мне здесь не хватает?

РЕДАКТИРОВАТЬ 2. Это пример кода. Выполняя это, я получаю, что BarService — это издевательская версия, а FooService — настоящая.

@FeignClient(name = "fooService")
public interface FooService {
}

@Component
public class BarService {
}

@Configuration
public class ConfigClass {

  @Bean
  public FooService fooService() {
    return Mockito.mock(FooService.class);
  }

  @Bean
  public BarService barService() {
    return Mockito.mock(BarService.class);
  }

@RunWith(SpringRunner.class)
@ActiveProfiles("test")
@SpringBootTest
public class TestClass {

  @Autowired
  private FooService fooService;

  @Autowired
  private BarService barService;

  @Test
  public void test() {
    System.out.println(fooService.getClass());
  }
}

person Juan Vega    schedule 30.05.2017    source источник
comment
Похоже, вам действительно нужно исправить эти причуды. Почему загрузка совершенно отдельного контекста может сломать другие тесты???   -  person M. Deinum    schedule 30.05.2017
comment
В основном проект использует Quartz, а тест использует Hibernate со встроенной базой данных H2. При запуске автоматически создает некоторые таблицы для Quartz через скрипт. При уничтожении контекстов один из них выдает исключение, потому что другой уже закрыл базу данных. Ничего страшного, но это оставляет уродливый журнал, и люди недовольны этим. Я точно не знаю, как это исправить, так как закрытие контекстов вызывает уничтожение базы данных, и я не знаю, могу ли я настроить дополнительный, чтобы избежать этого. Я думал, что этот подход будет быстрее   -  person Juan Vega    schedule 30.05.2017
comment
Кроме того, я не знаком с внутренностями @MockBean. Я предполагаю, что он создает только один дополнительный контекст для хранения макетов bean-компонентов и что он не создает новые каждый раз, когда тест определяет макет, который еще не был создан. Так ли это? Поскольку создание контекста фактически влияет на время, необходимое для запуска тестов, поскольку Hibernate был запущен с использованием сценариев sql, которые использует приложение.   -  person Juan Vega    schedule 30.05.2017
comment
Он создает новый свежий контекст на основе макетов, и, очевидно, это повлияет на производительность. Вы можете использовать контекстные иерархии, но не знаете, как это будет работать в среде Spring Boot. Но он создаст новый только для этого теста (контексты приложения кэшируются и повторно используются между тестами). @MockBean создает макет для bean-компонента и более тесно интегрируется с контекстом, автоматически сбрасывает макеты и т. д.   -  person M. Deinum    schedule 30.05.2017


Ответы (3)


Ваша проблема вызвана тем, что bean-компоненты FeignClient определены как primary, как объявление bean-компонента с @Primary. Таким образом, он имеет приоритет над другими обычными бобами. Начиная с версии spring-cloud-netflix 1.3.0, включенной в выпуск Dalston, вы можете отключить эту основную конфигурацию, как показано ниже.

@FeignClient(name = "fooService", primary = false)
public interface FooService {
}

Если вы измените свой код, как указано выше, Mocked bean-компонент будет внедрен в ваш тест.

Одна вещь, с которой вам нужно быть осторожным, это то, что опция primary используется, когда вы используете резервный компонент для своего FeignClient. Если у вас есть резервный компонент, вам может потребоваться указать компонент FeignClient с квалификатором, чтобы получить компонент FeignClient вместо резервного компонента.

Я думаю, что еще один способ внедрить имитированный bean-компонент вместо bean-компонента FeignClient для теста — использовать BeanPostProcessor примерно так, как показано ниже.

public static class MockProcessor implements BeanPostProcessor {
         : 
         :
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (/* distinguish your target bean */) 
            return Mockito.mock(FooService.class);
        return bean;
    }
}
person yongsung.yoon    schedule 01.06.2017
comment
Интересно, что это работает, но только если я определяю издевательскую версию как bean-компонент в производственном коде с аннотацией @Primary, которая будет автоматически распознаваться Spring. Если я создам класс @Configuration в моей тестовой папке и издевательскую версию внутри него, используя @Bean, Spring даже не загрузит его. Добавление дополнительного @Bean чего-либо еще загружается, поэтому дело не в том, что класс @Configuration упускается из виду. Вариант BeanPostProcessor тоже работает. - person Juan Vega; 01.06.2017

Аннотация @EnableFeignClients создаст первичные bean-компоненты для интерфейсов, отмеченных @FeignClient по умолчанию. Отключение этого для каждого интерфейса может иметь побочные эффекты, например, когда есть резервные компоненты.

Прежде всего, чтобы предотвратить создание bean-компонентов Feign, просто переместите эту аннотацию в класс конфигурации, который отключен для тестов.

@Configuration
@Profile("!test")
@EnableFeignClients
public class FeignEnable {

}

Затем аннотируйте свой тестовый класс с помощью @ActiveProfiles("test").

person Stephan Windmüller    schedule 18.08.2018
comment
Аннотация @Profile также доступна на уровне метода, так что только определенные компоненты Bean могут быть отключены/перезаписаны. - person dnl.re; 07.09.2020

было бы здорово, если бы вы могли поделиться своим тестовым классом.

Как я понял, ваша проблема в том, что вам нужно переопределить bean-компонент в вашем тесте.

Для этого вы можете обратиться к следующему вопросу:

Переопределение Autowired Bean в модульных тестах

person nikita_pavlenko    schedule 30.05.2017
comment
Я просто добавил пример проблемы. Как я уже сказал, я могу просто использовать @MockBean в тесте, но это приведет к дополнительным контекстам. И мне тоже интересно, почему так происходит. - person Juan Vega; 30.05.2017