Description
Overview
While working on #33925 and #34194, I noticed inconsistencies and peculiarities in the search algorithm for @TestBean
factory methods.
Specifically, for a @TestBean
field defined in a top-level class, the search for a factory method begins with the current test class (which may be a subclass of or or a @Nested
class defined in the class in which the @TestBean
field is declared). Whereas, for a @TestBean
field defined in a @Nested
test class, the search for a factory method begins with the @Nested
test class.
The above leads to inconsistent factory method discovery for @TestBean
factory methods.
In addition, the algorithm used for top-level classes results in "duplicate bean override" failures for cases which are clearly not duplicates but rather an "override of an override". In other words, a @TestBean
declaration in a subclass (which resolves to a factory method in the subclass) currently cannot override a seemingly identical @TestBean
declaration in a superclass (which resolves to a factory method in the superclass), which some might consider a bug.
Example
@SpringJUnitConfig
class BaseTests {
@TestBean
String enigma;
static String enigma() {
return "enigma in superclass";
}
@Test
void test() {
assertThat(enigma).isEqualTo("enigma in superclass");
}
@Nested
class NestedTests {
@TestBean
String enigma;
static String enigma() {
return "enigma in nested class";
}
@Test
void test() {
assertThat(enigma).isEqualTo("enigma in nested class");
}
}
@Configuration
static class Config {
@Bean
String enigma() {
return "enigma in @Configuration class";
}
}
}
class ExtendedTests extends BaseTests {
@TestBean
String enigma;
static String enigma() {
return "enigma in subclass";
}
@Test
@Override
void test() {
assertThat(enigma).isEqualTo("enigma in subclass");
}
}
BaseTests
passes as expected.BaseTests.NestedTests
currently passes because of the aforementioned inconsistent search algorithm.ExtendedTests
fails with the following error.
java.lang.IllegalStateException: Duplicate BeanOverrideHandler discovered in test class example.ExtendedTests: [TestBeanOverrideHandler@2dc3271b field = java.lang.String example.ExtendedTests.enigma, beanType = java.lang.String, beanName = [null], strategy = REPLACE_OR_CREATE, factoryMethod = enigma@ExtendedTests]
The cause of the exception is due to the fact that two @TestBean
override handlers resolve the same factory method enigma@ExtendedTests
; however, one might expect those to resolve to enigma@BaseTests
and enigma@ExtendedTests
(i.e., two distinct factory methods).
Proposal
- Resolve
@TestBean
factory methods consistently, beginning the search with the declaring class of the@TestBean
field.
One downside to the proposal is that @TestBean
factory methods would no longer be able to be resolved lazily in subclasses (somewhat like "late binding" to a concrete method), which would be a change in behavior.
Another option is to make the factory method search algorithm for @Nested
test classes align with the current semantics for top-level test classes.