Spring Boot: @TestConfiguration Not Overriding Bean During Integration Test
JavaSpring BootSpring TestJava Problem Overview
I have a Bean
defined in a class decorated with @Configuration
:
@Configuration
public class MyBeanConfig {
@Bean
public String configPath() {
return "../production/environment/path";
}
}
I have a class decorated with @TestConfiguration
that should override this Bean
:
@TestConfiguration
public class MyTestConfiguration {
@Bean
@Primary
public String configPath() {
return "/test/environment/path";
}
}
The configPath
bean is used to set the path to an external file containing a registration code that must be read during startup. It is used in an @Component
class:
@Component
public class MyParsingComponent {
private String CONFIG_PATH;
@Autowired
public void setCONFIG_PATH(String configPath) {
this.CONFIG_PATH = configPath;
}
}
While trying to debug this I set a breakpoint inside each method as well as the constructor of the test config class. The @TestConfiguration
's constructor breakpoint is hit, so i know that my test configuration class instantiates, however the configPath
method of that class is never hit. Instead, the configPath
method of the normal @Configuration
class is hit and the @Autowired
String
in MyParsingComponent
is always ../production/environment/path
rather than the expected /test/environment/path
.
Not sure why this is happening. Any thoughts would be greatly appreciated.
Java Solutions
Solution 1 - Java
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.
Solution 2 - Java
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.
Solution 3 - Java
- 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.
Solution 4 - Java
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
}
}
Solution 5 - Java
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.
Solution 6 - Java
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";
}
}
}