Skip to content

Duplicate BeanOverrideHandler discovered in @Nested test class hierarchy when upgrading to Spring 6.2.2 #34324

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
bwaldvogel opened this issue Jan 27, 2025 · 10 comments
Assignees
Labels
in: test Issues in the test module type: regression A bug that is also a regression
Milestone

Comments

@bwaldvogel
Copy link

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 #34204 which could be related to this issue.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jan 27, 2025
@sbrannen sbrannen changed the title Duplicate BeanOverrideHandler discovered in test class when upgrading to Spring 6.2.2 Duplicate BeanOverrideHandler discovered in @Nested test class hierarchy when upgrading to Spring 6.2.2 Jan 27, 2025
@sbrannen sbrannen added the in: test Issues in the test module label Jan 27, 2025
@sbrannen sbrannen self-assigned this Jan 27, 2025
@sbrannen
Copy link
Member

What happens if you declare BaseClassForNestedTests as a static nested class or as a stand-alone top-level class?

@sbrannen sbrannen added the status: waiting-for-feedback We need additional information before we can continue label Jan 27, 2025
@sbrannen
Copy link
Member

sbrannen commented Jan 27, 2025

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.

@sbrannen
Copy link
Member

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 9181cce for details).

@bwaldvogel
Copy link
Author

bwaldvogel commented Jan 27, 2025

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?

@NestedTestConfiguration(EnclosingConfiguration.OVERRIDE)
abstract class BaseClassForNestedTests {
	// ...
}

This fixes the error.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jan 27, 2025
@sbrannen sbrannen removed the status: feedback-provided Feedback has been provided label Jan 27, 2025
@sbrannen
Copy link
Member

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

@sbrannen sbrannen closed this as not planned Won't fix, can't repro, duplicate, stale Jan 30, 2025
@sbrannen sbrannen added status: invalid An issue that we don't feel is valid and removed status: waiting-for-triage An issue we've not yet triaged or decided on labels Jan 30, 2025
@bwaldvogel
Copy link
Author

bwaldvogel commented Jan 30, 2025

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).

@sbrannen sbrannen added type: regression A bug that is also a regression and removed status: invalid An issue that we don't feel is valid labels Jan 30, 2025
@sbrannen sbrannen added this to the 6.2.3 milestone Jan 30, 2025
@sbrannen
Copy link
Member

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.

@sbrannen sbrannen reopened this Jan 30, 2025
@aykborstelmann
Copy link

We stumbled across a circumstance for which the problem is still there. Do you want me to open a new issue or should we discuss it here ?

@sbrannen
Copy link
Member

@aykborstelmann, if you have discovered a bug that is still present in Spring Framework 6.2.6, please open a new issue with a reproducer.

Thanks

@aykborstelmann
Copy link

Alright, done in #34844.

sbrannen added a commit that referenced this issue May 2, 2025
Prior to this commit, bean overrides (such as @⁠MockitoBean, etc.) were
discovered multiple times if they were declared:

- at the type-level on an interface that is implemented at more than
  one level in the type hierarchy, the enclosing class hierarchy, or a
  combination of the type and enclosing class hierarchies.

or

- on a field declared in a class which can be reached multiple times
  while traversing the type and enclosing class hierarchies in
  scenarios such as the following: the class (X) in which the field is
  declared is a supertype of an enclosing type of the test class, and X
  is also an enclosing type of a supertype of the test class.

Such scenarios resulted in an IllegalStateException stating that a
duplicate BeanOverrideHandler was discovered.

To address that, this commit revises the search algorithm in
BeanOverrideHandler so that all types (superclasses, enclosing classes,
and implemented interfaces) are only visited once while traversing the
type and enclosing class hierarchies in search of bean override
handlers.

See gh-33925
See gh-34324
Closes gh-34844
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: test Issues in the test module type: regression A bug that is also a regression
Projects
None yet
Development

No branches or pull requests

4 participants