28
28
import org .junit .jupiter .api .extension .ExtensionContext ;
29
29
import org .junit .jupiter .params .support .AnnotationConsumer ;
30
30
import org .junit .platform .commons .JUnitException ;
31
+ import org .junit .platform .commons .PreconditionViolationException ;
31
32
import org .junit .platform .commons .util .CollectionUtils ;
32
33
import org .junit .platform .commons .util .Preconditions ;
33
34
import org .junit .platform .commons .util .ReflectionUtils ;
@@ -40,6 +41,9 @@ class MethodArgumentsProvider implements ArgumentsProvider, AnnotationConsumer<M
40
41
41
42
private String [] methodNames ;
42
43
44
+ private static final Predicate <Method > isFactoryMethod = //
45
+ method -> isConvertibleToStream (method .getReturnType ()) && !isTestMethod (method );
46
+
43
47
@ Override
44
48
public void accept (MethodSource annotation ) {
45
49
this .methodNames = annotation .value ();
@@ -52,28 +56,37 @@ public Stream<Arguments> provideArguments(ExtensionContext context) {
52
56
Object testInstance = context .getTestInstance ().orElse (null );
53
57
// @formatter:off
54
58
return stream (this .methodNames )
55
- .map (factoryMethodName -> getFactoryMethod (testClass , testMethod , factoryMethodName ))
59
+ .map (factoryMethodName -> findFactoryMethod (testClass , testMethod , factoryMethodName ))
56
60
.map (factoryMethod -> context .getExecutableInvoker ().invoke (factoryMethod , testInstance ))
57
61
.flatMap (CollectionUtils ::toStream )
58
62
.map (MethodArgumentsProvider ::toArguments );
59
63
// @formatter:on
60
64
}
61
65
62
- private Method getFactoryMethod (Class <?> testClass , Method testMethod , String factoryMethodName ) {
63
- if (!StringUtils .isBlank (factoryMethodName )) {
64
- if (looksLikeAFullyQualifiedMethodName (factoryMethodName )) {
65
- return getFactoryMethodByFullyQualifiedName (factoryMethodName );
66
- }
67
- else if (looksLikeALocalQualifiedMethodName (factoryMethodName )) {
68
- return getFactoryMethodByFullyQualifiedName (testClass .getName () + "#" + factoryMethodName );
69
- }
70
- }
71
- else {
72
- // User did not provide a factory method name, so we search for a
73
- // factory method with the same name as the parameterized test method.
66
+ private static Method findFactoryMethod (Class <?> testClass , Method testMethod , String factoryMethodName ) {
67
+ String originalFactoryMethodName = factoryMethodName ;
68
+
69
+ // If the user did not provide a factory method name, find a "default" local
70
+ // factory method with the same name as the parameterized test method.
71
+ if (StringUtils .isBlank (factoryMethodName )) {
74
72
factoryMethodName = testMethod .getName ();
73
+ return findFactoryMethodBySimpleName (testClass , testMethod , factoryMethodName );
75
74
}
76
- return findFactoryMethodBySimpleName (testClass , testMethod , factoryMethodName );
75
+
76
+ // Convert local factory method name to fully-qualified method name.
77
+ if (!looksLikeAFullyQualifiedMethodName (factoryMethodName )) {
78
+ factoryMethodName = testClass .getName () + "#" + factoryMethodName ;
79
+ }
80
+
81
+ // Find factory method using fully-qualified name.
82
+ Method factoryMethod = findFactoryMethodByFullyQualifiedName (testMethod , factoryMethodName );
83
+
84
+ // Ensure factory method has a valid return type and is not a test method.
85
+ Preconditions .condition (isFactoryMethod .test (factoryMethod ), () -> format (
86
+ "Could not find valid factory method [%s] for test class [%s] but found the following invalid candidate: %s" ,
87
+ originalFactoryMethodName , testClass .getName (), factoryMethod ));
88
+
89
+ return factoryMethod ;
77
90
}
78
91
79
92
private static boolean looksLikeAFullyQualifiedMethodName (String factoryMethodName ) {
@@ -90,52 +103,54 @@ private static boolean looksLikeAFullyQualifiedMethodName(String factoryMethodNa
90
103
return indexOfDot < indexOfOpeningParenthesis ;
91
104
}
92
105
// If we get this far, we conclude the supplied factory method name "looks"
93
- // like it was intended to be a fully qualified method name, even if the
106
+ // like it was intended to be a fully- qualified method name, even if the
94
107
// syntax is invalid. We do this in order to provide better diagnostics for
95
- // the user when a fully qualified method name is in fact invalid.
108
+ // the user when a fully- qualified method name is in fact invalid.
96
109
return true ;
97
110
}
98
111
99
- private static boolean looksLikeALocalQualifiedMethodName (String factoryMethodName ) {
100
- // This method is intended to be called after looksLikeAFullyQualifiedMethodName()
101
- // and therefore does not check for the absence of '#' and does not reason about
102
- // the presence or absence of a fully qualified class name.
103
- if (factoryMethodName .endsWith ("()" )) {
104
- return true ;
105
- }
106
- int indexOfLastOpeningParenthesis = factoryMethodName .lastIndexOf ('(' );
107
- return (indexOfLastOpeningParenthesis > 0 )
108
- && (indexOfLastOpeningParenthesis < factoryMethodName .lastIndexOf (')' ));
109
- }
110
-
111
- private Method getFactoryMethodByFullyQualifiedName (String fullyQualifiedMethodName ) {
112
+ private static Method findFactoryMethodByFullyQualifiedName (Method testMethod , String fullyQualifiedMethodName ) {
112
113
String [] methodParts = ReflectionUtils .parseFullyQualifiedMethodName (fullyQualifiedMethodName );
113
114
String className = methodParts [0 ];
114
115
String methodName = methodParts [1 ];
115
116
String methodParameters = methodParts [2 ];
117
+ Class <?> clazz = loadRequiredClass (className );
118
+
119
+ // Attempt to find an exact match first.
120
+ Method factoryMethod = ReflectionUtils .findMethod (clazz , methodName , methodParameters ).orElse (null );
121
+ if (factoryMethod != null ) {
122
+ return factoryMethod ;
123
+ }
124
+
125
+ boolean explicitParameterListSpecified = //
126
+ StringUtils .isNotBlank (methodParameters ) || fullyQualifiedMethodName .endsWith ("()" );
127
+
128
+ // If we didn't find an exact match but an explicit parameter list was specified,
129
+ // that's a user configuration error.
130
+ Preconditions .condition (!explicitParameterListSpecified ,
131
+ () -> format ("Could not find factory method [%s(%s)] in class [%s]" , methodName , methodParameters ,
132
+ className ));
116
133
117
- return ReflectionUtils . findMethod ( loadRequiredClass ( className ), methodName , methodParameters ). orElseThrow (
118
- () -> new JUnitException ( format ( "Could not find factory method [%s(%s)] in class [%s]" , methodName ,
119
- methodParameters , className )) );
134
+ // Otherwise, fall back to the same lenient search semantics that are used
135
+ // to locate a "default" local factory method.
136
+ return findFactoryMethodBySimpleName ( clazz , testMethod , methodName );
120
137
}
121
138
122
139
/**
123
- * Find all methods in the given {@code testClass} with the desired {@code factoryMethodName }
124
- * which have return types that can be converted to a {@link Stream}, ignoring the
125
- * {@code testMethod} itself as well as any {@code @Test }, {@code @TestTemplate},
126
- * or {@code @TestFactory} methods with the same name.
127
- * @return the factory method, if found
128
- * @throws org.junit.platform.commons.PreconditionViolationException if the
129
- * factory method was not found or if multiple competing factory methods with
130
- * the same name were found
140
+ * Find the factory method by searching for all methods in the given {@code clazz }
141
+ * with the desired {@code factoryMethodName} which have return types that can be
142
+ * converted to a {@link Stream }, ignoring the {@code testMethod} itself as well
143
+ * as any {@code @Test}, {@code @TestTemplate}, or {@code @TestFactory} methods
144
+ * with the same name.
145
+ * @return the single factory method matching the search criteria
146
+ * @throws PreconditionViolationException if the factory method was not found or
147
+ * multiple competing factory methods with the same name were found
131
148
*/
132
- private Method findFactoryMethodBySimpleName (Class <?> testClass , Method testMethod , String factoryMethodName ) {
149
+ private static Method findFactoryMethodBySimpleName (Class <?> clazz , Method testMethod , String factoryMethodName ) {
133
150
Predicate <Method > isCandidate = candidate -> factoryMethodName .equals (candidate .getName ())
134
151
&& !testMethod .equals (candidate );
135
- List <Method > candidates = ReflectionUtils .findMethods (testClass , isCandidate );
152
+ List <Method > candidates = ReflectionUtils .findMethods (clazz , isCandidate );
136
153
137
- Predicate <Method > isFactoryMethod = method -> isConvertibleToStream (method .getReturnType ())
138
- && !isTestMethod (method );
139
154
List <Method > factoryMethods = candidates .stream ().filter (isFactoryMethod ).collect (toList ());
140
155
141
156
Preconditions .condition (factoryMethods .size () > 0 , () -> {
@@ -145,23 +160,23 @@ private Method findFactoryMethodBySimpleName(Class<?> testClass, Method testMeth
145
160
if (candidates .size () > 0 ) {
146
161
return format (
147
162
"Could not find valid factory method [%s] in class [%s] but found the following invalid candidates: %s" ,
148
- factoryMethodName , testClass .getName (), candidates );
163
+ factoryMethodName , clazz .getName (), candidates );
149
164
}
150
165
// Otherwise, report that we didn't find anything.
151
- return format ("Could not find factory method [%s] in class [%s]" , factoryMethodName , testClass .getName ());
166
+ return format ("Could not find factory method [%s] in class [%s]" , factoryMethodName , clazz .getName ());
152
167
});
153
168
Preconditions .condition (factoryMethods .size () == 1 ,
154
169
() -> format ("%d factory methods named [%s] were found in class [%s]: %s" , factoryMethods .size (),
155
- factoryMethodName , testClass .getName (), factoryMethods ));
170
+ factoryMethodName , clazz .getName (), factoryMethods ));
156
171
return factoryMethods .get (0 );
157
172
}
158
173
159
- private boolean isTestMethod (Method candidate ) {
174
+ private static boolean isTestMethod (Method candidate ) {
160
175
return isAnnotated (candidate , Test .class ) || isAnnotated (candidate , TestTemplate .class )
161
176
|| isAnnotated (candidate , TestFactory .class );
162
177
}
163
178
164
- private Class <?> loadRequiredClass (String className ) {
179
+ private static Class <?> loadRequiredClass (String className ) {
165
180
return ReflectionUtils .tryToLoadClass (className ).getOrThrow (
166
181
cause -> new JUnitException (format ("Could not load class [%s]" , className ), cause ));
167
182
}
0 commit comments