Переопределение bean-компонентов Spring boot 2.1 против первичного

С Переопределение bean-компонентов Spring Boot 2.1 отключено по умолчанию, что хорошо.

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

Единственный способ, который я обнаружил, сработал, - это включить переопределение bean-компонентов через свойства приложения:

spring.main.allow-bean-definition-overriding=true

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

Бобы, которые я переопределяю, либо

  • Определен в другой конфигурации, импортированной в мою тестовую конфигурацию
  • Компонент, обнаруженный автоматически при сканировании аннотаций

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

Пример:

package com.stackoverflow.foo;
@Service
public class AService {
}

package com.stackoverflow.foo;
public class BService {
}

package com.stackoverflow.foo;
@Configuration
public BaseConfiguration {
    @Bean
    @Lazy
    public BService bService() {
        return new BService();
    }
}

package com.stackoverflow.bar;
@Configuration
@Import({BaseConfiguration.class})
public class TestConfiguration {
    @Bean
    public BService bService() {
        return Mockito.mock(BService.class);
    }
}

Вместо того, чтобы предоставлять свою собственную конфигурацию с макетом, почему бы не использовать @MockBean и позволить Spring Boot сделать замену. Поэтому вместо @Autowired BService bService сделайте @MockBean BService bService в своем коде. Сохраняет конфигурацию только для тестирования.

M. Deinum 05.11.2018 08:47

Комментарий М. Дейнума, приведенный выше, действительно решает проблему, но только тогда, когда вы действительно используете моки. Когда вам нужно переопределить bean-компонент в тестах - это не помогает.

Innokenty 04.12.2018 11:47

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

Alexander Radchenko 11.08.2020 01:15
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
36
3
62 263
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Ответ принят как подходящий

Переопределение bean-компонентов означает, что в контексте может быть только один bean-компонент с уникальным именем или идентификатором. Таким образом, вы можете предоставить два bean-компонента следующим образом:

package com.stackoverflow.foo;
@Configuration
public class BaseConfiguration {
   @Bean
   @Lazy
   public BService bService1() {
       return new BService();
   }
}

package com.stackoverflow.bar;
@Configuration
@Import({BaseConfiguration.class})
public class TestConfiguration {
    @Bean
    public BService bService2() {
        return Mockito.mock(BService.class);
    }
}

Если вы добавите @Primary, то первичный компонент будет внедрен по умолчанию в:

@Autowired
BService bService;

Привет, у меня тоже не работает. А что делать, если BaseConfiguration не публичный, а частный пакет?

Norbert Koch 18.04.2019 07:12

По умолчанию разрешено заменять @Component на @Bean. В твоем случае

@Service
public class AService {
}

@Component
public class BService {
    @Autowired
    public BService() { ... }
}

@Configuration
@ComponentScan
public BaseConfiguration {
}

@Configuration
// WARNING! Doesn't work with @SpringBootTest annotation
@Import({BaseConfiguration.class})
public class TestConfiguration {
    @Bean // you allowed to override @Component with @Bean.
    public BService bService() {
        return Mockito.mock(BService.class);
    }
}

Мне все еще нужно применить это свойство: spring.main.allow-bean-definition-overriding=true, а также @SpringBootTest(classes = {Application.class, TestConfiguration.class}), чтобы оно работало.

Duc Tran 25.01.2019 09:12

Определение класса TestConfiguration в @SpringBootTest наконец помогло мне. Спасибо Дык Тран

Terran 18.03.2020 17:43

spring.main.allow-bean-definition-overriding=true можно разместить в тестовых конфигурациях. Если вам нужно обширное интеграционное тестирование, вам нужно будет в какой-то момент переопределить bean-компоненты. Это неизбежно.

Хотя правильный ответ уже был предоставлен, это означает, что у вашего bean-компонента будут разные имена. Так что технически это не переопределение.

Если вам нужно реальное переопределение (потому что вы используете @Qualifiers, @Resources или что-то подобное), поскольку Spring Boot 2.X возможен только с использованием свойства spring.main.allow-bean-definition-overriding=true.

Обновлять: Будьте осторожны с Kotlin Bean Definition DSL. В Spring Boot потребуется настраиваемый ApplicationContextInitializer, например:

class BeansInitializer : ApplicationContextInitializer<GenericApplicationContext> {

    override fun initialize(context: GenericApplicationContext) =
            beans.initialize(context)

}

Теперь, если вы решите переопределить один из таких компонентов на основе DSL в своем тесте с помощью метода @Primary @Bean, этого не будет. Инициализатор сработает после методов @Bean, и вы все равно получите начальный компонент на основе DSL в своих тестах даже с @Primary на тестовом @Bean. Еще один вариант - также создать инициализатор теста для ваших тестов и перечислить их все в ваших свойствах теста, например (порядок имеет значение):

context:
    initializer:
        classes: com.yuranos.BeansInitializer, com.yuranos.TestBeansInitializer

DSL определения компонента также поддерживает первичное свойство через:

bean(isPrimary=true) {...}

- что вам нужно для устранения двусмысленности при попытке внедрить bean-компонент, однако main:allow-bean-definition-overriding: true не нужен, если вы идете по пути чистого DSL.

(Весенняя загрузка 2.1.3)

Я делаю тестовые bean-компоненты доступными только в профиле test и разрешаю переопределение только во время тестирования, например:

@ActiveProfiles("test")
@SpringBootTest(properties = {"spring.main.allow-bean-definition-overriding=true"})
class FooBarApplicationTests {

  @Test
  void contextLoads() {}
}

Бин, над которым я издеваюсь в тестовой конфигурации:

@Profile("test")
@Configuration
public class FooBarApplicationTestConfiguration {
  @Bean
  @Primary
  public SomeBean someBean() {
    return Mockito.mock(SomeBean.class);
  }
}

Другие вопросы по теме