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 DefaultHelloServiceTest with an empty @ContextConfiguration declaration 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.

  1. from the ApplicationContext for DefaultHelloServiceTest when running tests declared in DefaultHelloServiceTest (if you had such test methods).
  2. from the ApplicationContext for DefaultHelloServiceTest.TestBean when running tests declared in DefaultHelloServiceTest.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.