Skip to content

Commit 98cdf69

Browse files
committed
address PR comments
- add `writing-tests-parameterized-tests-argument-count-validation` section to the user guide. - cache configuration parameter in `ExtensionContext.Store` - adjust log and error messages Issue #3708
1 parent 3d40ebe commit 98cdf69

File tree

4 files changed

+102
-26
lines changed

4 files changed

+102
-26
lines changed

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2020,6 +2020,29 @@ The following annotations are repeatable:
20202020
* `@CsvFileSource`
20212021
* `@ArgumentsSource`
20222022

2023+
[[writing-tests-parameterized-tests-argument-count-validation]]
2024+
==== Argument Count Validation
2025+
2026+
WARNING: Argument count validation is currently an _experimental_ feature. You're invited to
2027+
give it a try and provide feedback to the JUnit team so they can improve and eventually
2028+
<<api-evolution, promote>> this feature.
2029+
2030+
By default, when an arguments source provides more arguments than the test method needs,
2031+
those additional arguments are ignored and the test executes as usual.
2032+
This can lead to bugs where arguments are never passed to the parameterized test method.
2033+
2034+
To prevent this, you can set argument count validation to 'strict'.
2035+
Then, any additional arguments will cause an error instead.
2036+
2037+
To change this behavior for all tests, set the `junit.jupiter.params.argumentCountValidation`
2038+
<<running-tests-config-params, configuration parameter>> to `strict`.
2039+
To change this behavior for a single test,
2040+
use the `argumentCountValidation` attribute of the `@ParameterizedTest` annotation:
2041+
2042+
[source,java,indent=0]
2043+
----
2044+
include::{testDir}/example/ParameterizedTestDemo.java[tags=argument_count_validation]
2045+
----
20232046

20242047
[[writing-tests-parameterized-tests-argument-conversion]]
20252048
==== Argument Conversion

documentation/src/test/java/example/ParameterizedTestDemo.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.junit.jupiter.api.extension.ExtensionContext;
5252
import org.junit.jupiter.api.extension.ParameterContext;
5353
import org.junit.jupiter.api.parallel.Execution;
54+
import org.junit.jupiter.params.ArgumentCountValidationMode;
5455
import org.junit.jupiter.params.ParameterizedTest;
5556
import org.junit.jupiter.params.aggregator.AggregateWith;
5657
import org.junit.jupiter.params.aggregator.ArgumentsAccessor;
@@ -607,4 +608,12 @@ static Stream<String> otherProvider() {
607608
return Stream.of("bar");
608609
}
609610
// end::repeatable_annotations[]
611+
612+
// tag::argument_count_validation[]
613+
@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT)
614+
@CsvSource({ "42, -666" })
615+
void testWithArgumentCountValidation(int number) {
616+
assertTrue(number > 0);
617+
}
618+
// end::argument_count_validation[]
610619
}

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

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
import static org.junit.platform.commons.support.AnnotationSupport.findRepeatableAnnotations;
1515

1616
import java.lang.reflect.Method;
17-
import java.util.Optional;
1817
import java.util.Arrays;
1918
import java.util.NoSuchElementException;
2019
import java.util.Optional;
@@ -70,7 +69,7 @@ public boolean supportsTestTemplate(ExtensionContext context) {
7069
+ "and before any arguments resolved by another ParameterResolver.",
7170
templateMethod.toGenericString()));
7271

73-
getStore(context).put(METHOD_CONTEXT_KEY, methodContext);
72+
getStoreInMethodNamespace(context).put(METHOD_CONTEXT_KEY, methodContext);
7473

7574
return true;
7675
}
@@ -108,14 +107,18 @@ public boolean mayReturnZeroTestTemplateInvocationContexts(ExtensionContext exte
108107
}
109108

110109
private ParameterizedTestMethodContext getMethodContext(ExtensionContext extensionContext) {
111-
return getStore(extensionContext)//
110+
return getStoreInMethodNamespace(extensionContext)//
112111
.get(METHOD_CONTEXT_KEY, ParameterizedTestMethodContext.class);
113112
}
114113

115-
private ExtensionContext.Store getStore(ExtensionContext context) {
114+
private ExtensionContext.Store getStoreInMethodNamespace(ExtensionContext context) {
116115
return context.getStore(Namespace.create(ParameterizedTestExtension.class, context.getRequiredTestMethod()));
117116
}
118117

118+
private ExtensionContext.Store getStoreInExtensionNamespace(ExtensionContext context) {
119+
return context.getStore(Namespace.create(ParameterizedTestExtension.class));
120+
}
121+
119122
private void validateArgumentCount(ExtensionContext extensionContext, Arguments arguments) {
120123
ArgumentCountValidationMode argumentCountValidationMode = getArgumentCountValidationMode(extensionContext);
121124
switch (argumentCountValidationMode) {
@@ -126,7 +129,7 @@ private void validateArgumentCount(ExtensionContext extensionContext, Arguments
126129
int testParamCount = extensionContext.getRequiredTestMethod().getParameterCount();
127130
int argumentsCount = arguments.get().length;
128131
Preconditions.condition(testParamCount == argumentsCount, () -> String.format(
129-
"Configuration error: the @ParameterizedTest has %s argument(s) but there were %s argument(s) provided./nNote: the provided arguments are %s",
132+
"Configuration error: the @ParameterizedTest has %s argument(s) but there were %s argument(s) provided.%nNote: the provided arguments are %s",
130133
testParamCount, argumentsCount, Arrays.toString(arguments.get())));
131134
break;
132135
default:
@@ -149,20 +152,33 @@ private ArgumentCountValidationMode getArgumentCountValidationMode(ExtensionCont
149152

150153
private ArgumentCountValidationMode getArgumentCountValidationModeConfiguration(ExtensionContext extensionContext) {
151154
String key = ARGUMENT_COUNT_VALIDATION_KEY;
152-
ArgumentCountValidationMode fallback = ArgumentCountValidationMode.DEFAULT;
153-
Optional<String> optionalValue = extensionContext.getConfigurationParameter(key);
154-
if (optionalValue.isPresent()) {
155-
String value = optionalValue.get();
156-
return Arrays.stream(ArgumentCountValidationMode.values()).filter(
157-
mode -> mode.name().equalsIgnoreCase(value)).findFirst().orElseGet(() -> {
155+
ArgumentCountValidationMode fallback = ArgumentCountValidationMode.NONE;
156+
ExtensionContext.Store store = getStoreInExtensionNamespace(extensionContext);
157+
return store.getOrComputeIfAbsent(key, k -> {
158+
Optional<String> optionalConfigValue = extensionContext.getConfigurationParameter(key);
159+
if (optionalConfigValue.isPresent()) {
160+
String configValue = optionalConfigValue.get();
161+
Optional<ArgumentCountValidationMode> enumValue = Arrays.stream(
162+
ArgumentCountValidationMode.values()).filter(
163+
mode -> mode.name().equalsIgnoreCase(configValue)).findFirst();
164+
if (enumValue.isPresent()) {
165+
logger.config(() -> String.format(
166+
"Using ArgumentCountValidationMode '%s' set via the '%s' configuration parameter.",
167+
enumValue.get().name(), key));
168+
return enumValue.get();
169+
}
170+
else {
158171
logger.warn(() -> String.format(
159-
"Ignored invalid configuration '%s' set via the '%s' configuration parameter.", value, key));
172+
"Invalid ArgumentCountValidationMode '%s' set via the '%s' configuration parameter. "
173+
+ "Falling back to the %s default value.",
174+
configValue, key, fallback.name()));
160175
return fallback;
161-
});
162-
}
163-
else {
164-
return fallback;
165-
}
176+
}
177+
}
178+
else {
179+
return fallback;
180+
}
181+
}, ArgumentCountValidationMode.class);
166182
}
167183

168184
private TestTemplateInvocationContext createInvocationContext(ParameterizedTestNameFormatter formatter,

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

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1117,35 +1117,52 @@ private EngineExecutionResults execute(String methodName, Class<?>... methodPara
11171117
class UnusedArgumentsWithStrictArgumentsCountIntegrationTests {
11181118
@Test
11191119
void failsWithArgumentsSourceProvidingUnusedArguments() {
1120-
var results = execute(UnusedArgumentsTestCase.class, "testWithTwoUnusedStringArgumentsProvider",
1120+
var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithTwoUnusedStringArgumentsProvider",
11211121
String.class);
11221122
results.allEvents().assertThatEvents() //
11231123
.haveExactly(1, event(EventConditions.finishedWithFailure(message(
1124-
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided./nNote: the provided arguments are [foo, unused1]"))));
1124+
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.\nNote: the provided arguments are [foo, unused1]"))));
11251125
}
11261126

11271127
@Test
11281128
void failsWithMethodSourceProvidingUnusedArguments() {
1129-
var results = execute(UnusedArgumentsTestCase.class, "testWithMethodSourceProvidingUnusedArguments",
1129+
var results = execute(ArgumentCountValidationMode.STRICT, UnusedArgumentsTestCase.class, "testWithMethodSourceProvidingUnusedArguments",
11301130
String.class);
11311131
results.allEvents().assertThatEvents() //
11321132
.haveExactly(1, event(EventConditions.finishedWithFailure(message(
1133-
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided./nNote: the provided arguments are [foo, unused1]"))));
1133+
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.\nNote: the provided arguments are [foo, unused1]"))));
1134+
}
1135+
1136+
@Test
1137+
void failsWithCsvSourceUnusedArgumentsAndStrictArgumentCountValidationAnnotationAttribute() {
1138+
var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, "testWithStrictArgumentCountValidation",
1139+
String.class);
1140+
results.allEvents().assertThatEvents() //
1141+
.haveExactly(1, event(EventConditions.finishedWithFailure(message(
1142+
"Configuration error: the @ParameterizedTest has 1 argument(s) but there were 2 argument(s) provided.\nNote: the provided arguments are [foo, unused1]"))));
1143+
}
1144+
1145+
@Test
1146+
void executesWithCsvSourceUnusedArgumentsAndArgumentCountValidationAnnotationAttribute() {
1147+
var results = execute(ArgumentCountValidationMode.NONE, UnusedArgumentsTestCase.class, "testWithNoneArgumentCountValidation", String.class);
1148+
results.allEvents().assertThatEvents() //
1149+
.haveExactly(1, event(test(), displayName("[1] argument=foo"), finishedWithFailure(message("foo"))));
11341150
}
11351151

11361152
@Test
11371153
void executesWithMethodSourceProvidingUnusedArguments() {
1138-
var results = execute(RepeatableSourcesTestCase.class, "testWithRepeatableCsvSource", String.class);
1154+
var results = execute(ArgumentCountValidationMode.STRICT, RepeatableSourcesTestCase.class, "testWithRepeatableCsvSource", String.class);
11391155
results.allEvents().assertThatEvents() //
11401156
.haveExactly(1, event(test(), displayName("[1] argument=a"), finishedWithFailure(message("a")))) //
11411157
.haveExactly(1, event(test(), displayName("[2] argument=b"), finishedWithFailure(message("b"))));
11421158
}
11431159

1144-
private EngineExecutionResults execute(Class<?> javaClass, String methodName,
1160+
private EngineExecutionResults execute(ArgumentCountValidationMode configurationValue, Class<?> javaClass, String methodName,
11451161
Class<?>... methodParameterTypes) {
1146-
return EngineTestKit.engine(new JupiterTestEngine()).selectors(
1147-
selectMethod(javaClass, methodName, methodParameterTypes)).configurationParameter(
1148-
ParameterizedTestExtension.ARGUMENT_COUNT_VALIDATION_KEY, "strict").execute();
1162+
return EngineTestKit.engine(new JupiterTestEngine()) //
1163+
.selectors(selectMethod(javaClass, methodName, methodParameterTypes)) //
1164+
.configurationParameter(ParameterizedTestExtension.ARGUMENT_COUNT_VALIDATION_KEY, configurationValue.name().toLowerCase()) //
1165+
.execute();
11491166
}
11501167
}
11511168

@@ -2065,6 +2082,17 @@ void testWithFieldSourceProvidingUnusedArguments(String argument) {
20652082
static Supplier<Stream<Arguments>> unusedArgumentsProviderField = //
20662083
() -> Stream.of(arguments("foo", "unused1"), arguments("bar", "unused2"));
20672084

2085+
@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.STRICT)
2086+
@CsvSource({ "foo, unused1" })
2087+
void testWithStrictArgumentCountValidation(String argument) {
2088+
fail(argument);
2089+
}
2090+
2091+
@ParameterizedTest(argumentCountValidation = ArgumentCountValidationMode.NONE)
2092+
@CsvSource({ "foo, unused1" })
2093+
void testWithNoneArgumentCountValidation(String argument) {
2094+
fail(argument);
2095+
}
20682096
}
20692097

20702098
static class LifecycleTestCase {

0 commit comments

Comments
 (0)