Hi,
I have a test class annotated with @SpringBootTest that uses a static inner @TestConfigurationclass to override a bean in the application context:
@SpringBootTest
class DefaultHelloServiceTest {
@TestConfiguration
static class MyTestConfiguration {
@Bean
@Primary
HelloService helloService() {
var helloService = new HelloService("Spring");
return helloService;
}
}
// ...
}
In Spring Boot 3.5.x MyTestConfiguration is evaluated even for tests in my test class, that are @Nested JUnit test classes:
@SpringBootTest
class DefaultHelloServiceTest {
// ... as above
@Nested
class TestBean {
@Test
void test_bean_nested() {
// ✅ WORKS in Spring Boot 3.5.x
// helloService is from MyTestConfiguration
assertEquals("Spring", helloService.getGreeting());
}
}
}
In Spring Boot 4 this does not work anymore. The MyTestConfiguration.helloService-Method is only called when a test method is executed, that is not inside a @Nested test class. Instead of my customized service the "normal" one from the application context is injected.
When adding an explicit @Import(DefaultHelloServiceTest.MyTestConfiguration.class) to the test class, it also works in both Spring Boot versions.
I have created a repository with two simple projects (3.5.7 and 4.0.0) that reproduces the behavior.
The two test classes:
- Spring Boot 4: https://github.com/nilshartmann/spring-testconfiguration-example/blob/main/spring-boot-4/src/test/java/nh/demo/testconfiguration/domain/DefaultHelloServiceTest.java
- Spring Boot 3.5.7: https://github.com/nilshartmann/spring-testconfiguration-example/blob/main/spring-boot-3/src/test/java/nh/demo/testconfiguration/domain/DefaultHelloServiceTest.java
Not sure what the correct behavior is and if I'm doing something wrong here, but for me it seems, that the behavior has changed.
Comment From: snicoll
Thanks for the report and the samples. It looks like we may need to adapt to something that has changed in Spring Framework.
Comment From: nilshartmann
@snicoll Sorry, hadn't read that document. When adding @SpringExtensionConfig(useTestClassScopedExtensionContext = true) it works. Not sure what that exactly means though 🥺
Comment From: snicoll
Thanks for testing @nilshartmann - This probably confirms that our integration test infrastructure has to adapt to the code change from Framework.
Comment From: sbrannen
When adding
@SpringExtensionConfig(useTestClassScopedExtensionContext = true)it works.
As a side note, if you additionally annotate DefaultHelloServiceTest with an empty @ContextConfiguration declaration it should work without @SpringExtensionConfig(useTestClassScopedExtensionContext = true).
So, please let me know if that works for you.
Furthermore, I was able to reproduce this without Spring Boot involved. So, I'll investigate this in Spring Framework.
Comment From: sbrannen
As a side note, if you additionally annotate
DefaultHelloServiceTestwith an empty@ContextConfigurationdeclaration it should work without@SpringExtensionConfig(useTestClassScopedExtensionContext = true).So, please let me know if that works for you.
Please note that the above is only a potential workaround.
It might not work for more involved use cases with Spring Boot's testing support.
Comment From: nilshartmann
Hi @sbrannen! Thanks for investigating. Adding @ContextConfiguration make the test working.
So I see now three different options that make the test working:
@Import(DefaultHelloServiceTest.MyTestConfiguration.class)
// or
@SpringExtensionConfig(useTestClassScopedExtensionContext = true)
// or
@ContextConfiguration
class DefaultHelloServiceTest { /* ... */ }
Comment From: sbrannen
With the changes made in Spring Framework 7.0 in conjunction with #35697, the HelloService field in DefaultHelloServiceTest is @Autowired from two different application contexts.
- from the
ApplicationContextforDefaultHelloServiceTestwhen running tests declared inDefaultHelloServiceTest(if you had such test methods). - from the
ApplicationContextforDefaultHelloServiceTest.TestBeanwhen running tests declared inDefaultHelloServiceTest.TestBean.
However, due to the bug described in #31456, the ApplicationContext for DefaultHelloServiceTest.TestBean does not use the MyTestConfiguration class in the enclosing class.
In other words, the switch to using a test-method scoped ExtensionContext causes you to suffer from that particular bug in your test suite.
Declaring @SpringExtensionConfig(useTestClassScopedExtensionContext = true) therefore effectively prevents your tests from suffering from that bug.
Similarly, annotating DefaultHelloServiceTest with either @Import(DefaultHelloServiceTest.MyTestConfiguration.class) or @ContextConfiguration prevents that bug from occurring, since the bootstrapping algorithms are then able to see those annotations, which causes the application contexts for DefaultHelloServiceTest and DefaultHelloServiceTest.TestBean to be the same context (i.e., they have the same context cache key).
I hope that helps to understand what's going on here.
In light of the above, I am closing this as a:
- Duplicate of #31456
Feel free to subscribe to that issue for further updates.