Spring Boot 4 nested tests prevent @Configuration bean loading

Summary

A Spring Boot 4 application fails to inject a String bean when a JUnit 5 @Nested test class is defined inside an abstract test base. The failure manifests as an UnsatisfiedDependencyException during the creation of the test class bean. The issue disappears when the nested test is removed, inheritance is eliminated, or the project is downgraded to Spring Boot 3.5.

Key takeaways

  • Spring Boot 4’s test context initializer does not process @Configuration classes declared inside a test that also contains @Nested test classes.
  • The test class is instantiated as a Spring bean, but the nested class prevents the inner @Configuration from being discovered.
  • The result is an unsatisfied dependency for any @Autowired fields defined in the parent test.

Root Cause

  • Spring Boot 4 test engine change: The underlying SpringExtension now creates a single test instance per test class hierarchy. When a nested test class exists, the parent class is treated as a test container rather than a candidate for configuration processing.
  • @Configuration scanning limitation: @SpringBootTest only scans configuration classes that are directly associated with the test class. The presence of @Nested causes the parent class to be loaded as a test template, so its static inner @Configuration (InternalConfig) is ignored.
  • Bean definition missing: Consequently, the String bean declared in InternalConfig is never registered, leading to UnsatisfiedDependencyException.

Why This Happens in Real Systems

  • Large codebases often use abstract test bases to share common utilities and configuration.
  • Teams add nested test suites to group related test scenarios, assuming inheritance and configuration continue to work.
  • Upgrading Spring Boot versions triggers subtle changes in the test context lifecycle that go unnoticed until a bean injection failure appears.
  • Production‑like integration tests that rely on custom beans defined in test configuration are especially vulnerable.

Real-World Impact

  • Failed CI pipelines: Tests that previously passed now break after a Spring Boot upgrade.
  • Hidden regression: The failure occurs only in nested test classes, making it hard to reproduce locally.
  • Reduced confidence: Developers may start disabling @SpringBootTest or removing useful test configuration to work around the issue.
  • Increased technical debt: Workarounds (e.g., duplicating configuration in each nested class) add maintenance overhead.

Example or Code (if necessary and relevant)

public abstract class AbstractTest {
    abstract void perform();

    @Nested
    class NestedTest {
        @Test
        void test() {
            perform();
        }
    }
}

@SpringBootTest(classes = MyTest.InternalConfig.class)
class MyTest extends AbstractTest {

    @Autowired
    private String myStr;

    @Override
    void perform() {
        assertThat(myStr).isNotNull();
    }

    @Configuration
    public static class InternalConfig {
        @Bean
        public String myStr() {
            return "test";
        }
    }
}

How Senior Engineers Fix It

  • Move configuration out of the test hierarchy
    Place @Configuration in a top‑level class (or a dedicated test-configuration package) and reference it explicitly in @SpringBootTest.
  • Use @TestConfiguration
    Annotate the inner static class with @TestConfiguration instead of @Configuration; Spring processes it even when nested.
  • Limit nesting
    Avoid placing @Nested classes inside a test that also serves as a configuration holder. Keep the test container separate.
  • Upgrade Spring Boot with the proper flag
    If staying on Spring Boot 4 is required, add @TestInstance(Lifecycle.PER_CLASS) to the parent test to force a single instance lifecycle and then declare the configuration with @Import.
  • Explicit import
    @SpringBootTest
    @Import(MyTest.InternalConfig.class)
    class MyTest extends AbstractTest { … }
  • Verify with a minimal reproducible test before merging any changes to ensure the bean is loaded.

Why Juniors Miss It

  • Assume inheritance works unchanged after a framework upgrade; they don’t check release notes for test context changes.
  • Overlook the effect of @Nested on the test instance lifecycle, treating it as a purely visual grouping tool.
  • Mix configuration and test logic in the same class, which is a habit from earlier Spring versions where it incidentally worked.
  • Rely on IDE auto‑completion that hides the missing bean error until the test is run in a CI environment.

Leave a Comment