Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ on GitHub.
* Dynamic tests now require less memory thanks to a number of improvements to internal
data structures.
* Documented constant values in `org.junit.jupiter.api.parallel.Resources`.

* In parameterized tests with `@MethodSource` or `@ArgumentSource`, arguments 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.

[[release-notes-5.8.0-M1-junit-vintage]]
=== JUnit Vintage
Expand Down
14 changes: 14 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -1634,6 +1634,20 @@ if they exceed the configured maximum length. The limit is configurable via the
`junit.jupiter.params.displayname.argument.maxlength` configuration parameter and defaults
to 512 characters.

When using `@MethodSource` or `@ArgumentSource`, you can give names to arguments. This
name will be used if the argument is included in the invocation display name, like in
the example below.

[source,java,indent=0]
----
include::{testDir}/example/ParameterizedTestDemo.java[tags=named_arguments]
----

....
A parameterized test with named arguments ✔
├─ 1: An important file ✔
└─ 2: Another file ✔
....

[[writing-tests-parameterized-tests-lifecycle-interop]]
==== Lifecycle and Interoperability
Expand Down
15 changes: 15 additions & 0 deletions documentation/src/test/java/example/ParameterizedTestDemo.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.junit.jupiter.params.provider.EnumSource.Mode.EXCLUDE;
import static org.junit.jupiter.params.provider.EnumSource.Mode.MATCH_ALL;

import java.io.File;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
Expand All @@ -39,6 +40,7 @@
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.TestReporter;
Expand Down Expand Up @@ -441,4 +443,17 @@ void testWithCustomAggregatorAnnotation(@CsvToPerson Person person) {
void testWithCustomDisplayNames(String fruit, int rank) {
}
// end::custom_display_names[]

// tag::named_arguments[]
@DisplayName("A parameterized test with named arguments")
@ParameterizedTest(name = "{index}: {0}")
@MethodSource("namedArguments")
void testWithNamedArguments(File file) {
}

static Stream<Arguments> namedArguments() {
return Stream.of(arguments(Named.of("An important file", new File("path1"))),
arguments(Named.of("Another file", new File("path2"))));
}
// end::named_arguments[]
}
48 changes: 48 additions & 0 deletions junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright 2015-2021 the original author or authors.
*
* All rights reserved. This program and the accompanying materials are
* made available under the terms of the Eclipse Public License v2.0 which
* accompanies this distribution and is available at
*
* https://www.eclipse.org/legal/epl-v20.html
*/

package org.junit.jupiter.api;

import static org.apiguardian.api.API.Status.STABLE;

import org.apiguardian.api.API;

/**
* {@code Named} is used to wrap an object and give it a name.
*
* @param <T> the type of the payload
*/
@API(status = STABLE, since = "5.8")
public interface Named<T> {

static <T> Named<T> of(String name, T payload) {
return new Named<T>() {
@Override
public String getName() {
return name;
}

@Override
public T getPayload() {
return payload;
}

@Override
public String toString() {
return name;
}
};
}

String getName();

T getPayload();

}
Original file line number Diff line number Diff line change
Expand Up @@ -143,8 +143,10 @@ protected static Stream<? extends Arguments> arguments(ArgumentsProvider provide

private Object[] consumedArguments(Object[] arguments, ParameterizedTestMethodContext methodContext) {
int parameterCount = methodContext.getParameterCount();
return methodContext.hasAggregator() ? arguments
: (arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments);
if (methodContext.hasAggregator()) {
return arguments;
}
return arguments.length > parameterCount ? Arrays.copyOf(arguments, parameterCount) : arguments;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Arrays;
import java.util.stream.IntStream;

import org.junit.jupiter.api.Named;
import org.junit.platform.commons.JUnitException;
import org.junit.platform.commons.util.StringUtils;

Expand Down Expand Up @@ -56,12 +57,19 @@ String format(int invocationIndex, Object... arguments) {
}

private String formatSafely(int invocationIndex, Object[] arguments) {
String pattern = prepareMessageFormatPattern(invocationIndex, arguments);
Object[] namedArguments = extractNamedArguments(arguments);
String pattern = prepareMessageFormatPattern(invocationIndex, namedArguments);
MessageFormat format = new MessageFormat(pattern);
Object[] humanReadableArguments = makeReadable(format, arguments);
Object[] humanReadableArguments = makeReadable(format, namedArguments);
return format.format(humanReadableArguments);
}

private Object[] extractNamedArguments(Object[] arguments) {
return Arrays.stream(arguments) //
.map(argument -> argument instanceof Named ? ((Named<?>) argument).getName() : argument) //
.toArray();
}

private String prepareMessageFormatPattern(int invocationIndex, Object[] arguments) {
String result = pattern//
.replace(DISPLAY_NAME_PLACEHOLDER, this.displayName)//
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@

import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.util.Arrays;

import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
Expand Down Expand Up @@ -60,8 +62,19 @@ public boolean supportsParameter(ParameterContext parameterContext, ExtensionCon
@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext)
throws ParameterResolutionException {
return this.methodContext.resolve(parameterContext, extractPayloads(this.arguments));
}

return this.methodContext.resolve(parameterContext, this.arguments);
@SuppressWarnings("unchecked")
private Object[] extractPayloads(Object[] arguments) {
return Arrays.stream(arguments) //
.map(argument -> {
if (argument instanceof Named) {
return ((Named<Object>) argument).getPayload();
}
return argument;
}) //
.toArray();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.MethodOrderer.OrderAnnotation;
import org.junit.jupiter.api.Named;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -625,6 +626,15 @@ private EngineExecutionResults execute(String methodName, Class<?>... methodPara
methodParameterTypes);
}

@Test
void namedParameters() {
execute("namedParameters", String.class).allEvents().assertThatEvents() //
.haveAtLeast(1,
event(test(), displayName("cool name"), finishedWithFailure(message("parameter value")))) //
.haveAtLeast(1,
event(test(), displayName("default name"), finishedWithFailure(message("default name"))));
}

}

@Nested
Expand Down Expand Up @@ -1009,6 +1019,12 @@ void streamOfTwoDimensionalObjectArrays(Object[][] array) {
fail(Arrays.deepToString(array));
}

@MethodSourceTest
@Order(13)
void namedParameters(String string) {
fail(string);
}

// ---------------------------------------------------------------------

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

static Stream<Arguments> namedParameters() {
return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name"));
}

// ---------------------------------------------------------------------

@MethodSourceTest
Expand Down