Skip to content

Commit 4d961fa

Browse files
committed
Find @⁠TestBean factory methods in multi-level @⁠Nested hierarchy
Prior to this commit, a @⁠TestBean factory method was found in the directly enclosing class for a @⁠Nested test class; however, such a factory method was not found in the enclosing class of the enclosing class, etc. This commit updates the search algorithm for @⁠TestBean factory methods so that it recursively searches the enclosing class hierarchy for @⁠Nested test classes. Closes gh-32951
1 parent f68130d commit 4d961fa

File tree

4 files changed

+179
-9
lines changed

4 files changed

+179
-9
lines changed

spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBean.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,10 @@
3636
*
3737
* <p>The instance is created from a zero-argument static factory method in the
3838
* test class whose return type is compatible with the annotated field. In the
39-
* case of a nested test class, the enclosing class is also considered. Similarly,
40-
* if the test class extends from a base class or implements any interfaces, the
41-
* entire type hierarchy is considered. The method is deduced as follows.
39+
* case of a nested test class, the enclosing class hierarchy is also considered.
40+
* Similarly, if the test class extends from a base class or implements any
41+
* interfaces, the entire type hierarchy is considered. The method is deduced as
42+
* follows.
4243
* <ul>
4344
* <li>If the {@link #methodName()} is specified, look for a static method with
4445
* that name.</li>

spring-test/src/main/java/org/springframework/test/context/bean/override/convention/TestBeanOverrideProcessor.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ static Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnTyp
7272
* <p>This method traverses up the type hierarchy of the given class in search
7373
* of the factory method, beginning with the class itself and then searching
7474
* implemented interfaces and superclasses. If a factory method is not found
75-
* in the type hierarchy, this method will also search on the enclosing class
76-
* if the class is a nested class.
75+
* in the type hierarchy, this method will also search the enclosing class
76+
* hierarchy if the class is a nested class.
7777
* <p>If multiple factory methods are found that match the search criteria,
7878
* an exception is thrown.
7979
* @param clazz the class in which to search for the factory method
@@ -91,9 +91,6 @@ static Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnTyp
9191
methodReturnType.isAssignableFrom(method.getReturnType()));
9292

9393
Set<Method> methods = findMethods(clazz, methodFilter);
94-
if (methods.isEmpty() && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
95-
methods = findMethods(clazz.getEnclosingClass(), methodFilter);
96-
}
9794

9895
int methodCount = methods.size();
9996
Assert.state(methodCount > 0, () -> """
@@ -139,7 +136,11 @@ public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Cl
139136

140137

141138
private static Set<Method> findMethods(Class<?> clazz, MethodFilter methodFilter) {
142-
return MethodIntrospector.selectMethods(clazz, methodFilter);
139+
Set<Method> methods = MethodIntrospector.selectMethods(clazz, methodFilter);
140+
if (methods.isEmpty() && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
141+
methods = findMethods(clazz.getEnclosingClass(), methodFilter);
142+
}
143+
return methods;
143144
}
144145

145146

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2002-2024 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.context.bean.override.convention;
18+
19+
import org.junit.jupiter.api.Nested;
20+
import org.junit.jupiter.api.Test;
21+
22+
import org.springframework.context.annotation.Bean;
23+
import org.springframework.context.annotation.Configuration;
24+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
28+
/**
29+
* {@link TestBean @TestBean} integration tests with multiple levels of
30+
* {@link Nested @Nested} test classes.
31+
*
32+
* @author Sam Brannen
33+
* @since 6.2
34+
*/
35+
@SpringJUnitConfig
36+
class MultipleNestingLevelsTestBeanIntegrationTests {
37+
38+
@TestBean(name = "field0", methodName = "testField0")
39+
String field0;
40+
41+
static String testField0() {
42+
return "zero";
43+
}
44+
45+
static String testField1() {
46+
return "one";
47+
}
48+
49+
static String testField2() {
50+
return "two";
51+
}
52+
53+
@Test
54+
void test() {
55+
assertThat(field0).isEqualTo("zero");
56+
}
57+
58+
59+
@Nested
60+
class NestedLevel1Tests {
61+
62+
@TestBean(name = "field1", methodName = "testField1")
63+
String field1;
64+
65+
@Test
66+
void test() {
67+
assertThat(field0).isEqualTo("zero");
68+
assertThat(field1).isEqualTo("one");
69+
}
70+
71+
@Nested
72+
class NestedLevel2Tests {
73+
74+
@TestBean(name = "field2", methodName = "testField2")
75+
String field2;
76+
77+
@Test
78+
void test() {
79+
assertThat(field0).isEqualTo("zero");
80+
assertThat(field1).isEqualTo("one");
81+
assertThat(field2).isEqualTo("two");
82+
}
83+
84+
@Nested
85+
class NestedLevel3Tests {
86+
87+
@TestBean(name = "field3", methodName = "testField2")
88+
String localField2;
89+
90+
// Local testField2() "hides" the method in the top-level enclosing class.
91+
static String testField2() {
92+
return "Local Two";
93+
}
94+
95+
@Test
96+
void test() {
97+
assertThat(field0).isEqualTo("zero");
98+
assertThat(field1).isEqualTo("one");
99+
assertThat(field2).isEqualTo("two");
100+
assertThat(localField2).isEqualTo("Local Two");
101+
}
102+
}
103+
}
104+
}
105+
106+
107+
@Configuration
108+
static class Config {
109+
110+
@Bean
111+
String field0() {
112+
return "replace me 0";
113+
}
114+
115+
@Bean
116+
String field1() {
117+
return "replace me 1";
118+
}
119+
120+
@Bean
121+
String field2() {
122+
return "replace me 2";
123+
}
124+
125+
@Bean
126+
String field3() {
127+
return "replace me 3";
128+
}
129+
}
130+
131+
}

spring-test/src/test/java/org/springframework/test/context/bean/override/convention/TestBeanIntegrationTests.java

+37
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,29 @@ void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
104104
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
105105
assertThat(TestBeanIntegrationTests.this.methodRenamed2).isEqualTo("nestedFieldOverride");
106106
}
107+
108+
@Nested
109+
@DisplayName("With @TestBean in the enclosing class of the enclosing class")
110+
public class TestBeanFieldInEnclosingClassLevel2Tests {
111+
112+
@Test
113+
void fieldHasOverride(ApplicationContext ctx) {
114+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
115+
assertThat(TestBeanIntegrationTests.this.nestedField).isEqualTo("nestedFieldOverride");
116+
}
117+
118+
@Test
119+
void renamedFieldHasOverride(ApplicationContext ctx) {
120+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
121+
assertThat(TestBeanIntegrationTests.this.renamed2).isEqualTo("nestedFieldOverride");
122+
}
123+
124+
@Test
125+
void fieldWithMethodNameHasOverride(ApplicationContext ctx) {
126+
assertThat(ctx.getBean("methodRenamed2")).as("applicationContext").isEqualTo("nestedFieldOverride");
127+
assertThat(TestBeanIntegrationTests.this.methodRenamed2).isEqualTo("nestedFieldOverride");
128+
}
129+
}
107130
}
108131

109132
@Nested
@@ -118,6 +141,20 @@ void fieldHasOverride(ApplicationContext ctx) {
118141
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
119142
assertThat(this.nestedField2).isEqualTo("nestedFieldOverride");
120143
}
144+
145+
@Nested
146+
@DisplayName("With factory method in the enclosing class of the enclosing class")
147+
public class TestBeanFactoryMethodInEnclosingClassLevel2Tests {
148+
149+
@TestBean(methodName = "nestedFieldTestOverride", name = "nestedField")
150+
String nestedField2;
151+
152+
@Test
153+
void fieldHasOverride(ApplicationContext ctx) {
154+
assertThat(ctx.getBean("nestedField")).as("applicationContext").isEqualTo("nestedFieldOverride");
155+
assertThat(this.nestedField2).isEqualTo("nestedFieldOverride");
156+
}
157+
}
121158
}
122159

123160

0 commit comments

Comments
 (0)