diff --git a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc index d13b4fb712bb..cbe30705d16a 100644 --- a/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc +++ b/documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc @@ -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 diff --git a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc index 221ae27805dc..e4cc3b40aae1 100644 --- a/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc +++ b/documentation/src/docs/asciidoc/user-guide/writing-tests.adoc @@ -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 diff --git a/documentation/src/test/java/example/ParameterizedTestDemo.java b/documentation/src/test/java/example/ParameterizedTestDemo.java index 62ff347d7e9a..8e1f851fc8a3 100644 --- a/documentation/src/test/java/example/ParameterizedTestDemo.java +++ b/documentation/src/test/java/example/ParameterizedTestDemo.java @@ -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; @@ -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; @@ -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 namedArguments() { + return Stream.of(arguments(Named.of("An important file", new File("path1"))), + arguments(Named.of("Another file", new File("path2")))); + } + // end::named_arguments[] } diff --git a/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java new file mode 100644 index 000000000000..cd2b6cfbccd5 --- /dev/null +++ b/junit-jupiter-api/src/main/java/org/junit/jupiter/api/Named.java @@ -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 the type of the payload + */ +@API(status = STABLE, since = "5.8") +public interface Named { + + static Named of(String name, T payload) { + return new Named() { + @Override + public String getName() { + return name; + } + + @Override + public T getPayload() { + return payload; + } + + @Override + public String toString() { + return name; + } + }; + } + + String getName(); + + T getPayload(); + +} diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java index e50d8bcfc0c7..7d110ce37805 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestExtension.java @@ -143,8 +143,10 @@ protected static Stream 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; } } diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java index 49cf34c39320..3f10d21d26c1 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestNameFormatter.java @@ -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; @@ -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)// diff --git a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java index d47889f8cd15..6185897d9d02 100644 --- a/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java +++ b/junit-jupiter-params/src/main/java/org/junit/jupiter/params/ParameterizedTestParameterResolver.java @@ -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; @@ -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) argument).getPayload(); + } + return argument; + }) // + .toArray(); } } diff --git a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java index 39332383d443..8423b3c23fb3 100644 --- a/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java +++ b/junit-jupiter-params/src/test/java/org/junit/jupiter/params/ParameterizedTestIntegrationTests.java @@ -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; @@ -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 @@ -1009,6 +1019,12 @@ void streamOfTwoDimensionalObjectArrays(Object[][] array) { fail(Arrays.deepToString(array)); } + @MethodSourceTest + @Order(13) + void namedParameters(String string) { + fail(string); + } + // --------------------------------------------------------------------- static Stream emptyMethodSource() { @@ -1066,6 +1082,10 @@ static Stream streamOfTwoDimensionalObjectArrays() { new Object[][] { { "five", 6 }, { "seven", 8 } }); } + static Stream namedParameters() { + return Stream.of(arguments(Named.of("cool name", "parameter value")), arguments("default name")); + } + // --------------------------------------------------------------------- @MethodSourceTest