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";
}
}

configPath bean 用于设置外部文件的路径,该文件包含启动过程中必须读取的注册代码。它在 @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中的 @Autowired String始终是 ../production/environment/path而不是预期的 /test/environment/path

不知道为什么会发生这样的事,如果你有任何想法,我将不胜感激。

68093 次浏览

As documented in the Detecting Test Configuration section of the Spring Boot reference manual, any beans configured in a top-level class annotated with @TestConfiguration will not be picked up via component scanning. So you have to explicitly register your @TestConfiguration class.

You can do that either via @Import(MyTestConfiguration.class) or @ContextConfiguration(classes = MyTestConfiguration.class) on your test class.

On the other hand, if your class annotated with @TestConfiguration were a static nested class within your test class, it would be registered automatically.

Make sure that the method name of your @Bean factory method does not match any existing bean name. I had issues with method names like config() or (in my case) prometheusConfig() which collided with existing bean names. Spring skips those factory methods silently and simply does not call them / does not instantiate the beans.

If you want to override a bean definition in your test, use the bean name explicitly as string parameter in your @Bean("beanName") annotation.

  • Test configuration has to be explicitly imported in the test via @Import({MyTestConfiguration.class}).
  • The name of the @Bean methods in @Configuration and @TestConfiguration have to be different. At least it makes difference in Spring Boot v2.2.
  • Also make sure spring.main.allow-bean-definition-overriding=true otherwise the bean could not be overriden.

For me worked this code:

  @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


}
}

I came across a similar issue recently and got it sorted out by annotating my testing bean with @Primary as well as @Bean. Not sure why it's required, which seems not documented in the Spring doc. The version of my SpringBoot is 2.0.3.

I struggled with a related problem, whereby even though I was using an inner static class, my test bean was not being registered.

It turns out, You still need to add your inner static class to the @ContextConfiguration class array, otherwise the beans inside the @TestConfiguration doesn't get picked up.

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";
}
}
}

In my case it was an issue with @RunWith(SpringRunner.class), I'm not exactly sure why it wasn't working, I was following this - Testing in Spring Boot

But after replacing that with @ExtendWith(SpringExtension.class) the inner static @TestConfiguration class created the beans as expected.

Maybe a version mismatch - I'm using Spring Boot 2.7.2.

In my case replacing @Import(TestConfig.class) with @ContextConfiguration(classes=TestConfig.class) did the trick. For some reason, some of the beans from TestConfig but 1 wasn't until I replaced @Import with @ContextConfiguration. This was also mentioned in some comments that were hidden because they had no upvotes.