|
27 | 27 | import java.util.List; |
28 | 28 | import java.util.function.Predicate; |
29 | 29 |
|
| 30 | +import org.jspecify.annotations.Nullable; |
30 | 31 | import org.junit.jupiter.api.TestInstance; |
| 32 | +import org.junit.jupiter.api.extension.ParameterResolutionException; |
31 | 33 | import org.junit.jupiter.params.provider.Arguments; |
32 | 34 | import org.junit.platform.commons.JUnitException; |
33 | 35 | import org.junit.platform.commons.support.HierarchyTraversalMode; |
@@ -130,6 +132,11 @@ public ResolverFacade getResolverFacade() { |
130 | 132 | @Override |
131 | 133 | public ParameterizedClassInvocationContext createInvocationContext(ParameterizedInvocationNameFormatter formatter, |
132 | 134 | Arguments arguments, int invocationIndex) { |
| 135 | + |
| 136 | + if (this.injectionType == InjectionType.FIELDS) { |
| 137 | + assertEnoughArgumentsForFieldInjection(arguments); |
| 138 | + } |
| 139 | + |
133 | 140 | return new ParameterizedClassInvocationContext(this, formatter, arguments, invocationIndex); |
134 | 141 | } |
135 | 142 |
|
@@ -172,6 +179,65 @@ private static <A extends Annotation> A getAnnotation(Method method, Class<A> an |
172 | 179 | .orElseThrow(() -> new JUnitException("Method not annotated with @" + annotationType.getSimpleName())); |
173 | 180 | } |
174 | 181 |
|
| 182 | + private void assertEnoughArgumentsForFieldInjection(Arguments arguments) { |
| 183 | + @SuppressWarnings("NullAway") |
| 184 | + final Object[] providedArguments = (arguments == null ? new Object[0] : arguments.get()); |
| 185 | + final int providedArgumentCount = providedArguments.length; |
| 186 | + |
| 187 | + final List<Field> parameterFields = findParameterAnnotatedFields(this.testClass); |
| 188 | + if (parameterFields.isEmpty()) { |
| 189 | + return; |
| 190 | + } |
| 191 | + |
| 192 | + final int requiredArgumentCount = requiredArgumentCountForParameterFields(parameterFields); |
| 193 | + if (providedArgumentCount >= requiredArgumentCount) { |
| 194 | + return; |
| 195 | + } |
| 196 | + |
| 197 | + final @Nullable Field firstMissingField = firstMissingParameterFieldByIndex(parameterFields, |
| 198 | + providedArgumentCount); |
| 199 | + |
| 200 | + final String missingTargetDescription = (firstMissingField != null) |
| 201 | + ? "field '%s' (index %d, type %s)".formatted(firstMissingField.getName(), |
| 202 | + firstMissingField.getAnnotation(Parameter.class).value(), firstMissingField.getType().getName()) |
| 203 | + : "parameter at index %d".formatted(providedArgumentCount); |
| 204 | + |
| 205 | + throw new ParameterResolutionException( |
| 206 | + "Not enough arguments for @ParameterizedClass field injection in %s: %s cannot be injected because only %d argument(s) were provided; at least %d are required.".formatted( |
| 207 | + this.testClass.getName(), missingTargetDescription, providedArgumentCount, requiredArgumentCount)); |
| 208 | + } |
| 209 | + |
| 210 | + private static int requiredArgumentCountForParameterFields(List<Field> parameterFields) { |
| 211 | + int maxIndex = -1; |
| 212 | + for (Field field : parameterFields) { |
| 213 | + Parameter param = field.getAnnotation(Parameter.class); |
| 214 | + if (param != null) { |
| 215 | + int index = param.value(); |
| 216 | + if (index >= 0 && index > maxIndex) { |
| 217 | + maxIndex = index; |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + return maxIndex + 1; |
| 222 | + } |
| 223 | + |
| 224 | + private static @Nullable Field firstMissingParameterFieldByIndex(List<Field> parameterFields, |
| 225 | + int providedArgumentCount) { |
| 226 | + Field candidate = null; |
| 227 | + int minIndex = Integer.MAX_VALUE; |
| 228 | + for (Field field : parameterFields) { |
| 229 | + Parameter param = field.getAnnotation(Parameter.class); |
| 230 | + if (param != null) { |
| 231 | + int index = param.value(); |
| 232 | + if (index >= 0 && index >= providedArgumentCount && index < minIndex) { |
| 233 | + candidate = field; |
| 234 | + minIndex = index; |
| 235 | + } |
| 236 | + } |
| 237 | + } |
| 238 | + return candidate; |
| 239 | + } |
| 240 | + |
175 | 241 | enum InjectionType { |
176 | 242 | CONSTRUCTOR, FIELDS |
177 | 243 | } |
|
0 commit comments