Skip to content

Named arguments in parameterized tests #2521

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 8, 2021
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