Skip to content

Commit d185bb1

Browse files
committed
Support @⁠TestBean factory methods defined in interfaces
Prior to this commit, @⁠TestBean factory methods were required to be defined in the test class or one of its superclasses. However, users may wish to define common factory methods in interfaces that can be shared easily across multiple test classes simply by implementing the necessary interface(s). This commit therefore switches from ReflectionUtils to MethodIntrospector to find @⁠TestBean factory methods in implemented interfaces within the type hierarchy of the test class. Closes gh-32943
1 parent 6a76141 commit d185bb1

File tree

3 files changed

+95
-10
lines changed

3 files changed

+95
-10
lines changed

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

+8-10
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Set;
2929

3030
import org.springframework.beans.factory.config.BeanDefinition;
31+
import org.springframework.core.MethodIntrospector;
3132
import org.springframework.core.ResolvableType;
3233
import org.springframework.lang.Nullable;
3334
import org.springframework.test.context.TestContextAnnotationUtils;
@@ -88,24 +89,23 @@ static Method findTestBeanFactoryMethod(Class<?> clazz, Class<?> methodReturnTyp
8889
supportedNames.contains(method.getName()) &&
8990
methodReturnType.isAssignableFrom(method.getReturnType()));
9091

91-
List<Method> methods = findMethods(clazz, methodFilter);
92+
Set<Method> methods = findMethods(clazz, methodFilter);
9293
if (methods.isEmpty() && TestContextAnnotationUtils.searchEnclosingClass(clazz)) {
9394
methods = findMethods(clazz.getEnclosingClass(), methodFilter);
9495
}
9596

96-
Assert.state(!methods.isEmpty(), () -> """
97+
int methodCount = methods.size();
98+
Assert.state(methodCount > 0, () -> """
9799
Failed to find a static test bean factory method in %s with return type %s \
98100
whose name matches one of the supported candidates %s""".formatted(
99101
clazz.getName(), methodReturnType.getName(), supportedNames));
100102

101-
long nameCount = methods.stream().map(Method::getName).distinct().count();
102-
int methodCount = methods.size();
103-
Assert.state(nameCount == 1, () -> """
103+
Assert.state(methodCount == 1, () -> """
104104
Found %d competing static test bean factory methods in %s with return type %s \
105105
whose name matches one of the supported candidates %s""".formatted(
106106
methodCount, clazz.getName(), methodReturnType.getName(), supportedNames));
107107

108-
return methods.get(0);
108+
return methods.iterator().next();
109109
}
110110

111111
@Override
@@ -138,10 +138,8 @@ public TestBeanOverrideMetadata createMetadata(Annotation overrideAnnotation, Cl
138138
}
139139

140140

141-
private static List<Method> findMethods(Class<?> clazz, MethodFilter methodFilter) {
142-
List<Method> methods = new ArrayList<>();
143-
ReflectionUtils.doWithMethods(clazz, methods::add, methodFilter);
144-
return methods;
141+
private static Set<Method> findMethods(Class<?> clazz, MethodFilter methodFilter) {
142+
return MethodIntrospector.selectMethods(clazz, methodFilter);
145143
}
146144

147145

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
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+
/**
20+
* Shared {@link TestBean @TestBean} factory methods.
21+
*
22+
* @author Sam Brannen
23+
* @since 6.2
24+
*/
25+
interface TestBeanFactory {
26+
27+
public static String createTestMessage() {
28+
return "test";
29+
}
30+
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
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.Test;
20+
21+
import org.springframework.context.annotation.Bean;
22+
import org.springframework.context.annotation.Configuration;
23+
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
24+
25+
import static org.assertj.core.api.Assertions.assertThat;
26+
27+
/**
28+
* {@link TestBean @TestBean} integration tests for test bean factory methods
29+
* defined in implemented interfaces.
30+
*
31+
* @author Sam Brannen
32+
* @since 6.2
33+
*/
34+
@SpringJUnitConfig
35+
public class TestBeanInterfaceIntegrationTests implements TestBeanFactory {
36+
37+
@TestBean(methodName = "createTestMessage")
38+
String message;
39+
40+
41+
@Test
42+
void test() {
43+
assertThat(message).isEqualTo("test");
44+
}
45+
46+
47+
@Configuration
48+
static class Config {
49+
50+
@Bean
51+
String message() {
52+
return "prod";
53+
}
54+
}
55+
56+
}

0 commit comments

Comments
 (0)