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
@Configurationclasses 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
@Configurationfrom being discovered. - The result is an unsatisfied dependency for any
@Autowiredfields defined in the parent test.
Root Cause
- Spring Boot 4 test engine change: The underlying
SpringExtensionnow 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. @Configurationscanning limitation:@SpringBootTestonly scans configuration classes that are directly associated with the test class. The presence of@Nestedcauses the parent class to be loaded as a test template, so its static inner@Configuration(InternalConfig) is ignored.- Bean definition missing: Consequently, the
Stringbean declared inInternalConfigis never registered, leading toUnsatisfiedDependencyException.
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
@SpringBootTestor 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@Configurationin a top‑level class (or a dedicatedtest-configurationpackage) and reference it explicitly in@SpringBootTest. - Use
@TestConfiguration
Annotate the inner static class with@TestConfigurationinstead of@Configuration; Spring processes it even when nested. - Limit nesting
Avoid placing@Nestedclasses 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
@Nestedon 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.