Skip to content

Commit 8f6587d

Browse files
committed
Move required parameter validation into ArgumentCountValidator
1 parent 3151b71 commit 8f6587d

File tree

4 files changed

+45
-56
lines changed

4 files changed

+45
-56
lines changed

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ArgumentCountValidator.java

Lines changed: 24 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import java.util.Arrays;
1414
import java.util.Optional;
1515

16+
import org.jspecify.annotations.Nullable;
1617
import org.junit.jupiter.api.extension.ExtensionConfigurationException;
1718
import org.junit.jupiter.api.extension.ExtensionContext;
1819
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
@@ -36,6 +37,7 @@ class ArgumentCountValidator {
3637
}
3738

3839
void validate(ExtensionContext extensionContext) {
40+
validateRequiredArgumentsArePresent();
3941
ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext);
4042
switch (argumentCountValidationMode) {
4143
case DEFAULT, NONE -> {
@@ -45,17 +47,34 @@ void validate(ExtensionContext extensionContext) {
4547
this.arguments);
4648
int totalCount = this.arguments.getTotalLength();
4749
Preconditions.condition(consumedCount == totalCount,
48-
() -> "Configuration error: @%s consumes %s %s but there %s %s %s provided.%nNote: the provided arguments were %s".formatted(
49-
this.declarationContext.getAnnotationName(), consumedCount,
50-
pluralize(consumedCount, "parameter", "parameters"), pluralize(totalCount, "was", "were"),
51-
totalCount, pluralize(totalCount, "argument", "arguments"),
52-
Arrays.toString(this.arguments.getAllPayloads())));
50+
() -> wrongNumberOfArgumentsMessages("consumes", consumedCount, null, null));
5351
}
5452
default -> throw new ExtensionConfigurationException(
5553
"Unsupported argument count validation mode: " + argumentCountValidationMode);
5654
}
5755
}
5856

57+
private void validateRequiredArgumentsArePresent() {
58+
var requiredParameterCount = this.declarationContext.getResolverFacade().getRequiredParameterCount();
59+
if (requiredParameterCount != null) {
60+
var totalCount = this.arguments.getTotalLength();
61+
Preconditions.condition(requiredParameterCount.value() <= totalCount,
62+
() -> wrongNumberOfArgumentsMessages("has", requiredParameterCount.value(), "required",
63+
requiredParameterCount.reason()));
64+
}
65+
}
66+
67+
private String wrongNumberOfArgumentsMessages(String verb, int actualCount, @Nullable String parameterAdjective,
68+
@Nullable String reason) {
69+
int totalCount = this.arguments.getTotalLength();
70+
return "Configuration error: @%s %s %s %s%s%s but there %s %s %s provided.%nNote: the provided arguments were %s".formatted(
71+
this.declarationContext.getAnnotationName(), verb, actualCount,
72+
parameterAdjective == null ? "" : parameterAdjective + " ",
73+
pluralize(actualCount, "parameter", "parameters"), reason == null ? "" : " (due to %s)".formatted(reason),
74+
pluralize(totalCount, "was", "were"), totalCount, pluralize(totalCount, "argument", "arguments"),
75+
Arrays.toString(this.arguments.getAllPayloads()));
76+
}
77+
5978
private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionContext extensionContext) {
6079
ArgumentCountValidationMode mode = declarationContext.getArgumentCountValidationMode();
6180
if (mode != ArgumentCountValidationMode.DEFAULT) {

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedClassInvocationContext.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,6 @@ private ClassTemplateConstructorParameterResolver createExtensionForConstructorI
6565
}
6666

6767
private Extension createExtensionForFieldInjection() {
68-
this.declarationContext.getResolverFacade().assertEnoughFieldArguments(this.arguments);
69-
7068
ResolverFacade resolverFacade = this.declarationContext.getResolverFacade();
7169
TestInstance.Lifecycle lifecycle = this.declarationContext.getTestInstanceLifecycle();
7270
return switch (lifecycle) {

junit-jupiter-params/src/main/java/org/junit/jupiter/params/ResolverFacade.java

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,9 @@ static ResolverFacade create(Class<?> clazz, List<Field> fields) {
9898
Stream.concat(uniqueIndexedParameters.values().stream(), aggregatorParameters.stream()) //
9999
.forEach(declaration -> makeAccessible(declaration.getField()));
100100

101-
return new ResolverFacade(clazz, uniqueIndexedParameters, aggregatorParameters, 0);
101+
var requiredParameterCount = new RequiredParameterCount(uniqueIndexedParameters.size(), "field injection");
102+
103+
return new ResolverFacade(clazz, uniqueIndexedParameters, aggregatorParameters, 0, requiredParameterCount);
102104
}
103105

104106
static ResolverFacade create(Constructor<?> constructor, ParameterizedClass annotation) {
@@ -155,27 +157,35 @@ else if (aggregatorParameters.isEmpty()) {
155157
}
156158
}
157159
return new ResolverFacade(executable, indexedParameters, new LinkedHashSet<>(aggregatorParameters.values()),
158-
indexOffset);
160+
indexOffset, null);
159161
}
160162

161163
private final int parameterIndexOffset;
162164
private final Map<ParameterDeclaration, Resolver> resolvers;
163165
private final DefaultParameterDeclarations indexedParameterDeclarations;
164166
private final Set<? extends ResolvableParameterDeclaration> aggregatorParameters;
167+
private final @Nullable RequiredParameterCount requiredParameterCount;
165168

166169
private ResolverFacade(AnnotatedElement sourceElement,
167170
NavigableMap<Integer, ? extends ResolvableParameterDeclaration> indexedParameters,
168-
Set<? extends ResolvableParameterDeclaration> aggregatorParameters, int parameterIndexOffset) {
171+
Set<? extends ResolvableParameterDeclaration> aggregatorParameters, int parameterIndexOffset,
172+
@Nullable RequiredParameterCount requiredParameterCount) {
169173
this.aggregatorParameters = aggregatorParameters;
170174
this.parameterIndexOffset = parameterIndexOffset;
171175
this.resolvers = new ConcurrentHashMap<>(indexedParameters.size() + aggregatorParameters.size());
172176
this.indexedParameterDeclarations = new DefaultParameterDeclarations(sourceElement, indexedParameters);
177+
this.requiredParameterCount = requiredParameterCount;
173178
}
174179

175180
ParameterDeclarations getIndexedParameterDeclarations() {
176181
return this.indexedParameterDeclarations;
177182
}
178183

184+
@Nullable
185+
RequiredParameterCount getRequiredParameterCount() {
186+
return this.requiredParameterCount;
187+
}
188+
179189
boolean isSupportedParameter(ParameterContext parameterContext, EvaluatedArgumentSet arguments) {
180190
int index = toLogicalIndex(parameterContext);
181191
if (this.indexedParameterDeclarations.get(index).isPresent()) {
@@ -464,43 +474,6 @@ private static ParameterResolutionException parameterResolutionException(String
464474
return new ParameterResolutionException(fullMessage, cause);
465475
}
466476

467-
private static void assertFieldAvailableOrThrow(FieldContext fieldContext, EvaluatedArgumentSet arguments) {
468-
int parameterIndex = fieldContext.getParameterIndex();
469-
int provided = arguments.getTotalLength();
470-
int required = parameterIndex + 1;
471-
472-
if (provided < required) {
473-
Field field = fieldContext.getField();
474-
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
475-
("Not enough arguments for @ParameterizedClass field injection: "
476-
+ "field '%s' (index %d, type %s) cannot be injected because "
477-
+ "only %d argument(s) were provided; at least %d are required.").formatted(field.getName(),
478-
parameterIndex, field.getType().getName(), provided, required));
479-
}
480-
}
481-
482-
void assertEnoughFieldArguments(EvaluatedArgumentSet arguments) {
483-
int provided = arguments.getTotalLength();
484-
int required = determineConsumedArgumentLength(Integer.MAX_VALUE);
485-
486-
if (provided < required) {
487-
int missingIndex = provided;
488-
var maybeDecl = getIndexedParameterDeclarations().get(missingIndex);
489-
if (maybeDecl.isPresent() && maybeDecl.get() instanceof FieldParameterDeclaration fpd) {
490-
Field field = fpd.getField();
491-
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
492-
("Not enough arguments for @ParameterizedClass field injection: "
493-
+ "field '%s' (index %d, type %s) cannot be injected because "
494-
+ "only %d argument(s) were provided; at least %d are required.").formatted(field.getName(),
495-
missingIndex, field.getType().getName(), provided, required));
496-
}
497-
throw new org.junit.jupiter.api.extension.ParameterResolutionException(
498-
"Not enough arguments for @ParameterizedClass field injection: "
499-
+ "index %d cannot be injected because only %d argument(s) were provided; at least %d are required.".formatted(
500-
missingIndex, provided, required));
501-
}
502-
}
503-
504477
private interface Resolver {
505478

506479
@Nullable
@@ -532,7 +505,6 @@ private record Converter(ArgumentConverter argumentConverter) implements Resolve
532505
@Override
533506
public @Nullable Object resolve(FieldContext fieldContext, ExtensionContext extensionContext,
534507
EvaluatedArgumentSet arguments, int invocationIndex) {
535-
ResolverFacade.assertFieldAvailableOrThrow(fieldContext, arguments);
536508

537509
Object argument = arguments.getConsumedPayload(fieldContext.getParameterIndex());
538510
try {
@@ -791,4 +763,7 @@ public boolean supports(ParameterContext parameterContext) {
791763
invocationIndex, Optional.of(parameterContext)));
792764
}
793765
}
766+
767+
record RequiredParameterCount(int value, String reason) {
768+
}
794769
}

jupiter-tests/src/test/java/org/junit/jupiter/params/ParameterizedClassIntegrationTests.java

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -557,14 +557,11 @@ void declaredIndexMustBeUnique() {
557557
void failsWithMeaningfulErrorWhenTooFewArgumentsProvidedForFieldInjection() {
558558
var results = executeTestsForClass(NotEnoughArgumentsForFieldsTestCase.class);
559559

560-
results.containerEvents().assertThatEvents()
561-
.haveExactly(1, event(finishedWithFailure(
562-
instanceOf(org.junit.jupiter.api.extension.ParameterResolutionException.class),
563-
message(it -> it.contains("field 's'")
564-
&& it.contains("index 1")
565-
&& it.contains("only 1 argument")
566-
&& it.contains("at least 2"))
567-
)));
560+
results.containerEvents().assertThatEvents() //
561+
.haveExactly(1, finishedWithFailure(message(
562+
"""
563+
Configuration error: @ParameterizedClass has 2 required parameters (due to field injection) but there was 1 argument provided.
564+
Note: the provided arguments were [1]""")));
568565
}
569566
}
570567

0 commit comments

Comments
 (0)