Skip to content

Commit 96302a7

Browse files
committed
Stop using a conventional suffix for TestBean factory methods
This commit changes how factory method for `@TestBean` usage is discovered. Previously the field name or bean name suffixed with 'TestOverride' was used. It sounds more natural to just use the field name or bean name, leaving cases where a suffix is required to explicitly providing the method name. As part of this change, the exception messages have been revisited as it's less since the method name candidates have the exact same name as the field or bean name. A `()` is added to make it more clear the name is for a method. Closes gh-32940
1 parent 5787bc5 commit 96302a7

12 files changed

+105
-104
lines changed

framework-docs/modules/ROOT/pages/testing/annotations/integration-spring/annotation-testbean.adoc

+33-9
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
`ApplicationContext` with an instance provided by a conventionally named static factory
66
method.
77

8-
By default, the associated factory method name is derived from the annotated field's name,
9-
but the annotation allows for a specific method name to be provided.
8+
The associated factory method name is derived from the annotated field's name, or bean
9+
name if specified. A `static` method with no argument that returns a type compatible
10+
with the type of the bean to override is expected. To make things more explicit, or if
11+
you'd rather use a different name, the annotation allows for a specific method name to
12+
be provided.
1013

11-
The `@TestBean` annotation uses the `REPLACE_DEFINITION`
12-
xref:testing/testcontext-framework/bean-overriding.adoc#testcontext-bean-overriding-custom[strategy for test bean overriding].
1314

1415
By default, the annotated field's type is used to search for candidate definitions to override.
1516
In that case it is required that exactly one definition matches, but note that `@Qualifier`
1617
annotations are also taken into account for the purpose of matching.
1718
Users can also make things entirely explicit by specifying a bean `name` in the annotation.
1819

19-
The following example shows how to fully configure the `@TestBean` annotation, with
20-
explicit values equivalent to the defaults:
20+
The following example shows how to use the default behavior of the `@TestBean` annotation:
2121

2222
[tabs]
2323
======
@@ -26,17 +26,41 @@ Java::
2626
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
2727
----
2828
class OverrideBeanTests {
29-
@TestBean(name = "service", methodName = "serviceTestOverride") // <1>
29+
@TestBean // <1>
3030
private CustomService service;
3131
3232
// test case body...
3333
34-
private static CustomService serviceTestOverride() { // <2>
34+
private static CustomService service() { // <2>
3535
return new MyFakeCustomService();
3636
}
3737
}
3838
----
39-
<1> Mark a field for bean overriding in this test class.
39+
<1> Mark a field for overriding of the bean with type `CustomService`.
40+
<2> The result of this static method will be used as the instance and injected into the field.
41+
======
42+
43+
44+
The following example shows how to fully configure the `@TestBean` annotation:
45+
46+
[tabs]
47+
======
48+
Java::
49+
+
50+
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
51+
----
52+
class OverrideBeanTests {
53+
@TestBean(name = "service", methodName = "createCustomService") // <1>
54+
private CustomService service;
55+
56+
// test case body...
57+
58+
private static CustomService createCustomService() { // <2>
59+
return new MyFakeCustomService();
60+
}
61+
}
62+
----
63+
<1> Mark a field for overriding of the bean with name `service`.
4064
<2> The result of this static method will be used as the instance and injected into the field.
4165
======
4266

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

+10-19
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@
4343
* <ul>
4444
* <li>If the {@link #methodName()} is specified, look for a static method with
4545
* that name.</li>
46-
* <li>If a method name is not specified, look for exactly one static method named
47-
* with a suffix equal to {@value #CONVENTION_SUFFIX} and starting with either the
48-
* name of the annotated field or the name of the bean (if specified).</li>
46+
* <li>If a method name is not specified, look for exactly one static method
47+
* named with either the name of the annotated field or the name of the bean
48+
* (if specified).</li>
4949
* </ul>
5050
*
5151
* <p>Consider the following example.
@@ -58,24 +58,24 @@
5858
*
5959
* // Tests
6060
*
61-
* private static CustomerRepository repositoryTestOverride() {
61+
* private static CustomerRepository repository() {
6262
* return new TestCustomerRepository();
6363
* }
6464
* }</code></pre>
6565
*
6666
* <p>In the example above, the {@code repository} bean is replaced by the
67-
* instance generated by the {@code repositoryTestOverride()} method. Not only
68-
* is the overridden instance injected into the {@code repository} field, but it
69-
* is also replaced in the {@code BeanFactory} so that other injection points
70-
* for that bean use the overridden bean instance.
67+
* instance generated by the {@code repository()} method. Not only is the
68+
* overridden instance injected into the {@code repository} field, but it is
69+
* also replaced in the {@code BeanFactory} so that other injection points for
70+
* that bean use the overridden bean instance.
7171
*
7272
* <p>To make things more explicit, the bean and method names can be set,
7373
* as shown in the following example.
7474
*
7575
* <pre><code>
7676
* class CustomerServiceTests {
7777
*
78-
* &#064;TestBean(name = "repository", methodName = "createTestCustomerRepository")
78+
* &#064;TestBean(name = "customerRepository", methodName = "createTestCustomerRepository")
7979
* private CustomerRepository repository;
8080
*
8181
* // Tests
@@ -97,14 +97,6 @@
9797
@BeanOverride(TestBeanOverrideProcessor.class)
9898
public @interface TestBean {
9999

100-
/**
101-
* Required suffix for the name of a factory method that overrides a bean
102-
* instance when the factory method is detected by convention.
103-
* @see #methodName()
104-
*/
105-
String CONVENTION_SUFFIX = "TestOverride";
106-
107-
108100
/**
109101
* Alias for {@link #name()}.
110102
* <p>Intended to be used when no other attributes are needed &mdash; for
@@ -130,8 +122,7 @@
130122
* also considered. Similarly, in case the test class inherits from a base
131123
* class the whole class hierarchy is considered.
132124
* <p>If left unspecified, the name of the factory method will be detected
133-
* based on convention.
134-
* @see #CONVENTION_SUFFIX
125+
* based either on the name of the annotated field or the name of the bean.
135126
*/
136127
String methodName() default "";
137128

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

+17-14
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,11 @@
2121
import java.lang.reflect.Method;
2222
import java.lang.reflect.Modifier;
2323
import java.util.ArrayList;
24+
import java.util.Collection;
2425
import java.util.LinkedHashSet;
2526
import java.util.List;
2627
import java.util.Set;
28+
import java.util.stream.Collectors;
2729

2830
import org.springframework.core.MethodIntrospector;
2931
import org.springframework.core.ResolvableType;
@@ -41,6 +43,7 @@
4143
*
4244
* @author Simon Baslé
4345
* @author Sam Brannen
46+
* @author Stephane Nicoll
4447
* @since 6.2
4548
*/
4649
class TestBeanOverrideProcessor implements BeanOverrideProcessor {
@@ -58,14 +61,14 @@ public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Cl
5861
overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), methodName);
5962
}
6063
else {
61-
// Otherwise, search for candidate factory methods using the convention
62-
// suffix and the field name or explicit bean name (if any).
64+
// Otherwise, search for candidate factory methods the field name
65+
// or explicit bean name (if any).
6366
List<String> candidateMethodNames = new ArrayList<>();
64-
candidateMethodNames.add(field.getName() + TestBean.CONVENTION_SUFFIX);
67+
candidateMethodNames.add(field.getName());
6568

6669
String beanName = testBeanAnnotation.name();
6770
if (StringUtils.hasText(beanName)) {
68-
candidateMethodNames.add(beanName + TestBean.CONVENTION_SUFFIX);
71+
candidateMethodNames.add(beanName);
6972
}
7073
overrideMethod = findTestBeanFactoryMethod(testClass, field.getType(), candidateMethodNames);
7174
}
@@ -75,7 +78,7 @@ public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Cl
7578

7679
/**
7780
* Find a test bean factory {@link Method} for the given {@link Class}.
78-
* <p>Delegates to {@link #findTestBeanFactoryMethod(Class, Class, List)}.
81+
* <p>Delegates to {@link #findTestBeanFactoryMethod(Class, Class, Collection)}.
7982
*/
8083
Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, String... methodNames) {
8184
return findTestBeanFactoryMethod(clazz, methodReturnType, List.of(methodNames));
@@ -104,7 +107,7 @@ Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, Stri
104107
* @throws IllegalStateException if a matching factory method cannot
105108
* be found or multiple methods match
106109
*/
107-
Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, List<String> methodNames) {
110+
Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, Collection<String> methodNames) {
108111
Assert.notEmpty(methodNames, "At least one candidate method name is required");
109112
Set<String> supportedNames = new LinkedHashSet<>(methodNames);
110113
MethodFilter methodFilter = method -> (Modifier.isStatic(method.getModifiers()) &&
@@ -113,16 +116,16 @@ Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnType, List
113116

114117
Set<Method> methods = findMethods(clazz, methodFilter);
115118

116-
Assert.state(!methods.isEmpty(), () -> """
117-
Failed to find a static test bean factory method in %s with return type %s \
118-
whose name matches one of the supported candidates %s""".formatted(
119-
clazz.getName(), methodReturnType.getName(), supportedNames));
119+
String methodNamesDescription = supportedNames.stream()
120+
.map(name -> name + "()").collect(Collectors.joining(" or "));
121+
Assert.state(!methods.isEmpty(), () ->
122+
"No static method found named %s in %s with return type %s".formatted(
123+
methodNamesDescription, clazz.getName(), methodReturnType.getName()));
120124

121125
long uniqueMethodNameCount = methods.stream().map(Method::getName).distinct().count();
122-
Assert.state(uniqueMethodNameCount == 1, () -> """
123-
Found %d competing static test bean factory methods in %s with return type %s \
124-
whose name matches one of the supported candidates %s""".formatted(
125-
uniqueMethodNameCount, clazz.getName(), methodReturnType.getName(), supportedNames));
126+
Assert.state(uniqueMethodNameCount == 1, () ->
127+
"Found %d competing static methods named %s in %s with return type %s".formatted(
128+
uniqueMethodNameCount, methodNamesDescription, clazz.getName(), methodReturnType.getName()));
126129

127130
return methods.iterator().next();
128131
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,11 @@ abstract class AbstractTestBeanIntegrationTestCase {
3232
@TestBean(name = "thirdBean")
3333
Pojo anotherBean;
3434

35-
static Pojo otherBeanTestOverride() {
35+
static Pojo otherBean() {
3636
return new FakePojo("otherBean in superclass");
3737
}
3838

39-
static Pojo thirdBeanTestOverride() {
39+
static Pojo thirdBean() {
4040
return new FakePojo("third in superclass");
4141
}
4242

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ static class NoMatchingBeansTestCase {
8080
void test() {
8181
}
8282

83-
static ExampleService exampleTestOverride() {
83+
static ExampleService example() {
8484
return fail("unexpected override");
8585
}
8686

@@ -100,7 +100,7 @@ static class TooManyBeansTestCase {
100100
void test() {
101101
}
102102

103-
static ExampleService exampleTestOverride() {
103+
static ExampleService example() {
104104
return fail("unexpected override");
105105
}
106106

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

+7-11
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import org.junit.jupiter.api.Test;
2020

21+
import org.springframework.test.context.bean.override.convention.AbstractTestBeanIntegrationTestCase.Pojo;
2122
import org.springframework.test.context.junit.EngineTestKitUtils;
2223

2324
import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure;
@@ -40,10 +41,8 @@ void failsIfFieldInSupertypeButNoMethod() {
4041
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
4142
finishedWithFailure(
4243
instanceOf(IllegalStateException.class),
43-
message("""
44-
Failed to find a static test bean factory method in %s with return type %s \
45-
whose name matches one of the supported candidates [someBeanTestOverride]"""
46-
.formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))));
44+
message("No static method found named someBean() in %s with return type %s"
45+
.formatted(FieldInSupertypeButNoMethodTestCase.class.getName(), Pojo.class.getName()))));
4746
}
4847

4948
@Test
@@ -52,11 +51,8 @@ void failsIfMethod1InSupertypeAndMethod2InType() {
5251
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
5352
finishedWithFailure(
5453
instanceOf(IllegalStateException.class),
55-
message("""
56-
Found 2 competing static test bean factory methods in %s with return type %s \
57-
whose name matches one of the supported candidates \
58-
[anotherBeanTestOverride, thirdBeanTestOverride]"""
59-
.formatted(testClass.getName(), AbstractTestBeanIntegrationTestCase.Pojo.class.getName()))));
54+
message("Found 2 competing static methods named anotherBean() or thirdBean() in %s with return type %s"
55+
.formatted(Method1InSupertypeAndMethod2InTypeTestCase.class.getName(), Pojo.class.getName()))));
6056
}
6157

6258

@@ -69,11 +65,11 @@ void test() {
6965

7066
static class Method1InSupertypeAndMethod2InTypeTestCase extends AbstractTestBeanIntegrationTestCase {
7167

72-
static Pojo someBeanTestOverride() {
68+
static Pojo someBean() {
7369
return new FakePojo("ignored");
7470
}
7571

76-
static Pojo anotherBeanTestOverride() {
72+
static Pojo anotherBean() {
7773
return new FakePojo("sub2");
7874
}
7975

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

+12-16
Original file line numberDiff line numberDiff line change
@@ -66,25 +66,21 @@ void testBeanFailingNoExplicitNameBean() {
6666

6767
@Test
6868
void testBeanFailingNoImplicitMethod() {
69-
Class<?> testClass = ExplicitTestOverrideMethodNotPresentTestCase.class;
69+
Class<?> testClass = ExplicitOverrideMethodNotPresentTestCase.class;
7070
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
7171
finishedWithFailure(
7272
instanceOf(IllegalStateException.class),
73-
message("""
74-
Failed to find a static test bean factory method in %s with return type \
75-
java.lang.String whose name matches one of the supported candidates \
76-
[notPresent]""".formatted(testClass.getName()))));
73+
message("No static method found named notPresent() in %s with return type %s"
74+
.formatted(testClass.getName(), String.class.getName()))));
7775
}
7876

7977
@Test
8078
void testBeanFailingNoExplicitMethod() {
81-
Class<?> testClass = ImplicitTestOverrideMethodNotPresentTestCase.class;
79+
Class<?> testClass = ImplicitOverrideMethodNotPresentTestCase.class;
8280
EngineTestKitUtils.executeTestsForClass(testClass).assertThatEvents().haveExactly(1,
8381
finishedWithFailure(instanceOf(IllegalStateException.class),
84-
message("""
85-
Failed to find a static test bean factory method in %s with return type \
86-
java.lang.String whose name matches one of the supported candidates \
87-
[fieldTestOverride]""".formatted(testClass.getName()))));
82+
message("No static method found named field() in %s with return type %s"
83+
.formatted(testClass.getName(), String.class.getName()))));
8884
}
8985

9086
@Test
@@ -111,7 +107,7 @@ void test() {
111107
fail("should fail earlier");
112108
}
113109

114-
static String noOriginalBeanTestOverride() {
110+
static String noOriginalBean() {
115111
return "should be ignored";
116112
}
117113
}
@@ -127,13 +123,13 @@ void test() {
127123
fail("should fail earlier");
128124
}
129125

130-
static String notPresentTestOverride() {
126+
static String notPresent() {
131127
return "should be ignored";
132128
}
133129
}
134130

135131
@SpringJUnitConfig
136-
static class ExplicitTestOverrideMethodNotPresentTestCase {
132+
static class ExplicitOverrideMethodNotPresentTestCase {
137133

138134
@TestBean(methodName = "notPresent")
139135
String field;
@@ -145,9 +141,9 @@ void test() {
145141
}
146142

147143
@SpringJUnitConfig
148-
static class ImplicitTestOverrideMethodNotPresentTestCase {
144+
static class ImplicitOverrideMethodNotPresentTestCase {
149145

150-
@TestBean // expects fieldTestOverride method
146+
@TestBean // expects field method
151147
String field;
152148

153149
@Test
@@ -167,7 +163,7 @@ void test() {
167163
fail("should fail earlier");
168164
}
169165

170-
static String fieldTestOverride() {
166+
static String field() {
171167
return "should be ignored";
172168
}
173169

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ public class TestBeanByTypeIntegrationTests {
5151
@CustomQualifier
5252
StringBuilder anyNameForStringBuilder2;
5353

54-
static ExampleService anyNameForServiceTestOverride() {
54+
static ExampleService anyNameForService() {
5555
return new RealExampleService("Mocked greeting");
5656
}
5757

0 commit comments

Comments
 (0)