Skip to content

Commit 65008db

Browse files
juliette-derancourtrunningcode
authored andcommitted
Named arguments in parameterized tests (junit-team#2521)
This commit introduces a `Named` interface, which is used to wrap test arguments and give them names. Thanks to this, arguments in parameterized tests with `@MethodSource` or `@ArgumentSource` can now have optional names. When the argument is included in the display name of an iteration, this name will be used instead of the value.
1 parent 72f433d commit 65008db

File tree

8 files changed

+128
-6
lines changed

8 files changed

+128
-6
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,9 @@ on GitHub.
8383
* Dynamic tests now require less memory thanks to a number of improvements to internal
8484
data structures.
8585
* Documented constant values in `org.junit.jupiter.api.parallel.Resources`.
86-
86+
* In parameterized tests with `@MethodSource` or `@ArgumentSource`, arguments can now have
87+
optional names. When the argument is included in the display name of an iteration, this
88+
name will be used instead of the value.
8789

8890
[[release-notes-5.8.0-M1-junit-vintage]]
8991
=== JUnit Vintage

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1634,6 +1634,20 @@ if they exceed the configured maximum length. The limit is configurable via the
16341634
`junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults
16351635
to 512 characters.
16361636

1637+
When using `@MethodSource` or `@ArgumentSource`, you can give names to arguments. This
1638+
name will be used if the argument is included in the invocation display name, like in
1639+
the example below.
1640+
1641+
[source,java,indent=0]
1642+
----
1643+
include::{testDir}/example/ParameterizedTestDemo.java[tags=named_arguments]
1644+
----
1645+
1646+
....
1647+
A parameterized test with named arguments ✔
1648+
├─ 1: An important file ✔
1649+
└─ 2: Another file ✔
1650+
....
16371651

16381652
[[writing-tests-parameterized-tests-lifecycle-interop]]
16391653
==== Lifecycle and Interoperability

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
2020
import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL;
2121

22+
import java.io.File;
2223
import java.lang.annotation.ElementType;
2324
import java.lang.annotation.Retention;
2425
import java.lang.annotation.RetentionPolicy;
@@ -39,6 +40,7 @@
3940
import org.junit.jupiter.api.AfterEach;
4041
import org.junit.jupiter.api.BeforeEach;
4142
import org.junit.jupiter.api.DisplayName;
43+
import org.junit.jupiter.api.Named;
4244
import org.junit.jupiter.api.Nested;
4345
import org.junit.jupiter.api.TestInfo;
4446
import org.junit.jupiter.api.TestReporter;
@@ -441,4 +443,17 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
441443
void testWithCustomDisplayNames(String fruit, int rank) {
442444
}
443445
// end::custom_display_names[]
446+
447+
// tag::named_arguments[]
448+
@DisplayName("A parameterized test with named arguments")
449+
@ParameterizedTest(name = "{index}: {0}")
450+
@MethodSource("namedArguments")
451+
void testWithNamedArguments(File file) {
452+
}
453+
454+
static Stream<Arguments> namedArguments() {
455+
return Stream.of(arguments(Named.of("An important file", new File("path1"))),
456+
arguments(Named.of("Another file", new File("path2"))));
457+
}
458+
// end::named_arguments[]
444459
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2015-2021 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.api;
12+
13+
import static org.apiguardian.api.API.Status.STABLE;
14+
15+
import org.apiguardian.api.API;
16+
17+
/**
18+
* {@code Named} is used to wrap an object and give it a name.
19+
*
20+
* @param <T> the type of the payload
21+
*/
22+
@API(status = STABLE, since = "5.8")
23+
public interface Named<T> {
24+
25+
static <T> Named<T> of(String name, T payload) {
26+
return new Named<T>() {
27+
@Override
28+
public String getName() {
29+
return name;
30+
}
31+
32+
@Override
33+
public T getPayload() {
34+
return payload;
35+
}
36+
37+
@Override
38+
public String toString() {
39+
return name;
40+
}
41+
};
42+
}
43+
44+
String getName();
45+
46+
T getPayload();
47+
48+
}

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,8 +143,10 @@ protected static Stream<? extends Arguments> arguments(ArgumentsProvider provide
143143

144144
private Object[] consumedArguments(Object[] arguments, ParameterizedTestMethodContext methodContext) {
145145
int parameterCount = methodContext.getParameterCount();
146-
return methodContext.hasAggregator() ? arguments
147-
: (arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments);
146+
if (methodContext.hasAggregator()) {
147+
return arguments;
148+
}
149+
return arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments;
148150
}
149151

150152
}

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

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Arrays;
2222
import java.util.stream.IntStream;
2323

24+
import org.junit.jupiter.api.Named;
2425
import org.junit.platform.commons.JUnitException;
2526
import org.junit.platform.commons.util.StringUtils;
2627

@@ -56,12 +57,19 @@ String format(int invocationIndex, Object... arguments) {
5657
}
5758

5859
private String formatSafely(int invocationIndex, Object[] arguments) {
59-
String pattern = prepareMessageFormatPattern(invocationIndex, arguments);
60+
Object[] namedArguments = extractNamedArguments(arguments);
61+
String pattern = prepareMessageFormatPattern(invocationIndex, namedArguments);
6062
MessageFormat format = new MessageFormat(pattern);
61-
Object[] humanReadableArguments = makeReadable(format, arguments);
63+
Object[] humanReadableArguments = makeReadable(format, namedArguments);
6264
return format.format(humanReadableArguments);
6365
}
6466

67+
private Object[] extractNamedArguments(Object[] arguments) {
68+
return Arrays.stream(arguments) //
69+
.map(argument -> argument instanceof Named ? ((Named<?>) argument).getName() : argument) //
70+
.toArray();
71+
}
72+
6573
private String prepareMessageFormatPattern(int invocationIndex, Object[] arguments) {
6674
String result = pattern//
6775
.replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)//

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

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@
1212

1313
import java.lang.reflect.Executable;
1414
import java.lang.reflect.Method;
15+
import java.util.Arrays;
1516

17+
import org.junit.jupiter.api.Named;
1618
import org.junit.jupiter.api.extension.ExtensionContext;
1719
import org.junit.jupiter.api.extension.ParameterContext;
1820
import org.junit.jupiter.api.extension.ParameterResolutionException;
@@ -60,8 +62,19 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon
6062
@Override
6163
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
6264
throws ParameterResolutionException {
65+
return this.methodContext.resolve(parameterContext, extractPayloads(this.arguments));
66+
}
6367

64-
return this.methodContext.resolve(parameterContext, this.arguments);
68+
@SuppressWarnings("unchecked")
69+
private Object[] extractPayloads(Object[] arguments) {
70+
return Arrays.stream(arguments) //
71+
.map(argument -> {
72+
if (argument instanceof Named) {
73+
return ((Named<Object>) argument).getPayload();
74+
}
75+
return argument;
76+
}) //
77+
.toArray();
6578
}
6679

6780
}

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
import org.junit.jupiter.api.BeforeAll;
4747
import org.junit.jupiter.api.BeforeEach;
4848
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
49+
import org.junit.jupiter.api.Named;
4950
import org.junit.jupiter.api.Nested;
5051
import org.junit.jupiter.api.Order;
5152
import org.junit.jupiter.api.Test;
@@ -625,6 +626,15 @@ private EngineExecutionResults execute(String methodName, Class<?>... methodPara
625626
methodParameterTypes);
626627
}
627628

629+
@Test
630+
void namedParameters() {
631+
execute("namedParameters", String.class).allEvents().assertThatEvents() //
632+
.haveAtLeast(1,
633+
event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) //
634+
.haveAtLeast(1,
635+
event(test(), displayName("default name"), finishedWithFailure(message("default name"))));
636+
}
637+
628638
}
629639

630640
@Nested
@@ -1009,6 +1019,12 @@ void streamOfTwoDimensionalObjectArrays(Object[][] array) {
10091019
fail(Arrays.deepToString(array));
10101020
}
10111021

1022+
@MethodSourceTest
1023+
@Order(13)
1024+
void namedParameters(String string) {
1025+
fail(string);
1026+
}
1027+
10121028
// ---------------------------------------------------------------------
10131029

10141030
static Stream<Arguments> emptyMethodSource() {
@@ -1066,6 +1082,10 @@ static Stream<Object[][]> streamOfTwoDimensionalObjectArrays() {
10661082
new Object[][] { { "five", 6 }, { "seven", 8 } });
10671083
}
10681084

1085+
static Stream<Arguments> namedParameters() {
1086+
return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name"));
1087+
}
1088+
10691089
// ---------------------------------------------------------------------
10701090

10711091
@MethodSourceTest

0 commit comments

Comments
 (0)