Spring Boot:@TestConfiguration集成测试期间不覆盖 Bean



我有一个用@Configuration装饰的类中定义的Bean

@Configuration
public class MyBeanConfig {
@Bean
public String configPath() {
return "../production/environment/path";
}
}

我有一个用@TestConfiguration装饰的类,应该覆盖这个Bean

@TestConfiguration
public class MyTestConfiguration {
@Bean
@Primary
public String configPath() {
return "/test/environment/path";
}
}

configPathBean 用于设置包含启动期间必须读取的注册码的外部文件的路径。它用于@Component类:

@Component
public class MyParsingComponent {
private String CONFIG_PATH;

@Autowired
public void setCONFIG_PATH(String configPath) {
this.CONFIG_PATH = configPath;
}
}

在尝试调试时,我在每个方法以及测试配置类的构造函数中设置了一个断点。@TestConfiguration的构造函数断点被命中,所以我知道我的测试配置类实例化,但是该类的configPath方法永远不会命中。相反,命中了正常@Configuration类的configPath方法,并且MyParsingComponent中的@AutowiredString始终../production/environment/path而不是预期的/test/environment/path

不知道为什么会这样。任何想法将不胜感激。

如 Spring 引导参考手册的"检测测试配置"部分所述,在带有@TestConfiguration注释的顶级类中配置的任何 bean 都不会通过组件扫描来拾取。因此,您必须显式注册@TestConfiguration类。

您可以通过测试类的@Import(MyTestConfiguration.class)@ContextConfiguration(classes = MyTestConfiguration.class)来执行此操作。

另一方面,如果使用@TestConfiguration批注的类是测试类中的static嵌套类,则会自动注册该类。

确保@Bean工厂方法的方法名称与任何现有的 Bean 名称都不匹配。我对config()或(在我的情况下)等方法名称有问题prometheusConfig()与现有的 bean 名称发生冲突。Spring 静默地跳过这些工厂方法,只是不调用它们/不实例化 bean。

如果要在测试中覆盖 Bean 定义,请在 @Bean("beanName") 注释中显式使用 Bean 名称作为字符串参数。

  • 测试配置必须通过@Import({MyTestConfiguration.class})在测试中显式导入。
  • @Configuration@TestConfiguration@Bean方法的名称必须不同。至少它在 Spring Boot v2.2 中有所不同。
  • 还要确保spring.main.allow-bean-definition-overriding=true否则无法覆盖 bean。

对我来说,这段代码:

@TestConfiguration // 1. necessary
public class TestMessagesConfig {
@Bean
@Primary // 2. necessary
public MessageSource testMessageSource() { // 3. different method name than in production code e.g. add test prefix
}
}

我遇到了一个相关的问题,即使我使用了内部静态类,我的测试 Bean 也没有被注册。

事实证明,您仍然需要将内部静态类添加到@ContextConfiguration类数组中,否则@TestConfiguration内的 bean 不会被拾取。

public interface Foo {
String execute();
}
public class FooService {
private final Foo foo;
FooService(Foo foo) {
this.foo = foo;
}
public String execute() {
return foo.execute();
}
}
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {FooService.class, FooTest.FooTestConfig.class})
public class FooTest {
@Autowired
FooService fooService;
@Test
void test() {
Assertions.assertEquals("MY_TEST_BEAN", fooService.execute());
}
@TestConfiguration
static class FooTestConfig {
@Bean
public Foo getFooBean() {
return () -> "MY_TEST_BEAN";
}
}
}

我最近遇到了类似的问题,并通过用@Primary和@Bean注释我的测试豆来解决这个问题。不知道为什么需要它,这似乎没有记录在 Spring 文档中。我的SpringBoot版本是2.0.3。

在我的情况下,将@Import(TestConfig.class)替换为@ContextConfiguration(classes=TestConfig.class)做到了。出于某种原因,TestConfig 中的一些豆子,但 1 直到我用@ContextConfiguration替换了@Import才出现。 在一些隐藏的评论中也提到了这一点,因为他们没有赞成票。

我觉得奇怪的是,有几个答案说@Bean的名称必须彼此不同。这将如何使一个覆盖另一个?
没有一个特定的答案对我有用,但我通过结合他们的一些建议解决了这个问题。

这是对我有用的方法。

主要配置类:

@Configuration
public class SpringConfiguration {
@Bean
BeanInterface myBean() {
return new BeanImplementation();
}

@Bean
OtherClass otherBean() {
return new OtherClass();
}
}

测试配置类:

@TestConfiguration
public class TestSpringConfiguration {
@Bean
@Primary
BeanInterface myBean() {
return new TestBeanImplementation();
}
}

测试类:

@SpringBootTest(classes = TestSpringConfiguration.class, 
properties = "spring.main.allow-bean-definition-overriding=true")
public class Tests {

@Test
public void test() {
// do stuff
}
}

这样,"myBean"bean实例是TestSpringConfiguration类中定义的实例,而"otherBean"是SpringConfiguration类中定义的实例,因为它没有被覆盖.
如果我给"myBean"bean两个不同的名称,"真正的"bean仍然会被初始化,并且, 就我而言,会在测试期间给出错误,因为它需要仅在运行时在其适当环境中可用的东西.
一旦我给出了两个相同的名称,Spring 就会抛出一个错误,说它们冲突。因此,为什么我必须在测试类的@SpringBootTest注释中指定属性spring.main.allow-bean-definition-overriding=true



顺便说一句,如果你没有使用Spring Boot,我想这些替代注释可能对你有用:

@ExtendWith(value = SpringExtension.class)
@ContextConfiguration(loader = AnnotationConfigContextLoader.class, // <- not sure about this one
classes = { SpringConfiguration.class, TestSpringConfiguration.class })
public class Tests {

@Test
public void test() {
// do stuff
}
}

然后,您仍然必须在测试application.ymlapplication.properties文件中设置属性spring.main.allow-bean-definition-overriding=true,或者在启动时通过代码以某种其他方式设置属性。

注意:我不是100%确定你需要loader = AnnotationConfigContextLoader.class的东西。首先尝试没有它。我在我的一个项目中需要它,这个项目有 Spring Without Boot,但我不记得这是一个标准的东西,还是出于某种特定原因我需要它。

就我而言,这是@RunWith(SpringRunner.class)的问题,我不确定为什么它不起作用,我正在关注这个 - 在 Spring 启动中进行测试

但是在用@ExtendWith(SpringExtension.class)替换它之后,内部静态@TestConfiguration类按预期创建了 bean。

也许是版本不匹配 - 我正在使用 Spring Boot 2.7.2。

最新更新