Consider the following test with nested test classes:
@SpringBootTest
class SomeTest {
@MockitoSpyBean
SomeService someService;
abstract class BaseClassForNestedTests {
@Test
void someTest() {}
}
@Nested
class SomeNestedTest extends BaseClassForNestedTests {}
}
@Service
class SomeService {
}
When upgrading Spring Boot 3.4.1 to 3.4.2 (which upgrades Spring from 6.2.1 to 6.2.2), the test starts to fail with:
Duplicate BeanOverrideHandler discovered in test class SomeTest$SomeNestedTest: [MockitoSpyBeanOverrideHandler@5c48c0c0 field = SomeService SomeTest.someService, beanType = SomeService, beanName = [null], strategy = WRAP, reset = AFTER]
java.lang.IllegalStateException: Duplicate BeanOverrideHandler discovered in test class SomeTest$SomeNestedTest: [MockitoSpyBeanOverrideHandler@5c48c0c0 field = SomeService SomeTest.someService, beanType = SomeService, beanName = [null], strategy = WRAP, reset = AFTER]
at org.springframework.util.Assert.state(Assert.java:101)
at org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactory.lambda$findBeanOverrideHandlers$1(BeanOverrideContextCustomizerFactory.java:55)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactory.findBeanOverrideHandlers(BeanOverrideContextCustomizerFactory.java:54)
at org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactory.createContextCustomizer(BeanOverrideContextCustomizerFactory.java:46)
at org.springframework.test.context.bean.override.BeanOverrideContextCustomizerFactory.createContextCustomizer(BeanOverrideContextCustomizerFactory.java:38)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.getContextCustomizers(AbstractTestContextBootstrapper.java:360)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:332)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildDefaultMergedContextConfiguration(AbstractTestContextBootstrapper.java:267)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildMergedContextConfiguration(AbstractTestContextBootstrapper.java:215)
at org.springframework.test.context.support.AbstractTestContextBootstrapper.buildTestContext(AbstractTestContextBootstrapper.java:108)
at org.springframework.boot.test.context.SpringBootTestContextBootstrapper.buildTestContext(SpringBootTestContextBootstrapper.java:111)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:142)
at org.springframework.test.context.TestContextManager.<init>(TestContextManager.java:126)
at org.springframework.test.context.junit.jupiter.SpringExtension.getTestContextManager(SpringExtension.java:363)
at org.springframework.test.context.junit.jupiter.SpringExtension.beforeAll(SpringExtension.java:128)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
I tried to do a search to find if this issue was already reported. I found https://github.com/spring-projects/spring-framework/issues/34204 which could be related to this issue.
Comment From: sbrannen
What happens if you declare BaseClassForNestedTests
as a static
nested class or as a stand-alone top-level class?
Comment From: sbrannen
Similarly, what happens if you annotate your non-static nested base class with @NestedTestConfiguration
, as follows?
@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
abstract class BaseClassForNestedTests {
// ...
}
Alternatively, you should be able to annotate SomeNestedTest
with @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
to achieve the same effect.
Comment From: sbrannen
I tried to do a search to find if this issue was already reported. I found #34204 which could be related to this issue.
The change in behavior that you have reported is not related to #34204 but rather results from the changes made to BeanOverrideContextCustomizerFactory
and BeanOverrideHandler
in conjunction with #33925 (see commit 9181cce65f17ad2c41861444d0c8417adecf36a1 for details).
Comment From: bwaldvogel
What happens if you declare BaseClassForNestedTests as a static nested class or as a stand-alone top-level class?
This would work, however, in my real-world case I cannot make the base class static.
Similarly, what happens if you annotate your non-static nested base class with
@NestedTestConfiguration
, as follows?java @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE) abstract class BaseClassForNestedTests { // ... }
This fixes the error.
Comment From: sbrannen
This fixes the error.
Thanks for confirming that @NestedTestConfiguration
allows you to address that scenario.
In light of that, I am closing this issue as "works as designed".
Cheers,
Sam
Comment From: bwaldvogel
Oh, I’m actually surprised to be honest and thought that this behavior change will be addressed. I think from a developer point of view it will be quite surprising and I’m sure that many developers will not know why the exception occurs and how to fix it (i.e. adding the @NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
at the right place).
Comment From: sbrannen
Thanks for the feedback, @bwaldvogel.
Although the current behavior (introduced in 6.2.2) may be considered technically correct in terms of the search algorithm, it is also technically a "regression" in behavior for test bean overrides, and as you mentioned, users may not find it readily apparent how an enclosing class can be visited twice by the search algorithm.
In light of that, I am reopening this issue with the plan to not search in the enclosing class hierarchy of a supertype of a @Nested
test class, which should allow your original use case to work without modification.