Skip to content
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

Make parameterized ArgumentSource annotations repeatable #3787

Merged
merged 20 commits into from May 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -45,8 +45,9 @@ JUnit repository on GitHub.
[[release-notes-5.11.0-M2-junit-jupiter-new-features-and-improvements]]
==== New Features and Improvements

* ❓

* Support `@..Source` annotations as repeatable for parameterized tests. See the
<<../user-guide/index.adoc#writing-tests-parameterized-repeatable-sources, User Guide>>
for more details.

[[release-notes-5.11.0-M2-junit-vintage]]
=== JUnit Vintage
Expand Down
28 changes: 28 additions & 0 deletions documentation/src/docs/asciidoc/user-guide/writing-tests.adoc
Expand Up @@ -1963,6 +1963,34 @@ If you wish to implement a custom `ArgumentsProvider` that also consumes an anno
(like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`),
you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class.

[[writing-tests-parameterized-repeatable-sources]]
===== Multiple sources using repeatable annotations
Repeatable annotations provide a convenient way to specify multiple sources from
different providers.

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

Following the above parameterized test, a test case will run for each argument:

----
[1] foo
[2] bar
----

The following annotations are repeatable:

* `@ValueSource`
* `@EnumSource`
* `@MethodSource`
* `@FieldSource`
* `@CsvSource`
* `@CsvFileSource`
* `@ArgumentsSource`


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

Expand Down
18 changes: 18 additions & 0 deletions documentation/src/test/java/example/ParameterizedTestDemo.java
Expand Up @@ -542,4 +542,22 @@ static Stream<Arguments> namedArguments() {
}
// end::named_arguments[]
// @formatter:on

// tag::repeatable_annotations[]
@DisplayName("A parameterized test that makes use of repeatable annotations")
@ParameterizedTest
@MethodSource("someProvider")
@MethodSource("otherProvider")
void testWithRepeatedAnnotation(String argument) {
assertNotNull(argument);
}

static Stream<String> someProvider() {
return Stream.of("foo");
}

static Stream<String> otherProvider() {
return Stream.of("bar");
}
// end::repeatable_annotations[]
}
Expand Up @@ -13,6 +13,8 @@
import static org.apiguardian.api.API.Status.EXPERIMENTAL;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import org.apiguardian.api.API;
Expand All @@ -39,17 +41,17 @@ public abstract class AnnotationBasedArgumentsProvider<A extends Annotation>
public AnnotationBasedArgumentsProvider() {
}

private A annotation;
private final List<A> annotations = new ArrayList<>();

@Override
public final void accept(A annotation) {
Preconditions.notNull(annotation, "annotation must not be null");
this.annotation = annotation;
annotations.add(annotation);
}

@Override
public final Stream<? extends Arguments> provideArguments(ExtensionContext context) {
return provideArguments(context, this.annotation);
return annotations.stream().flatMap(annotation -> provideArguments(context, annotation));
}

/**
Expand Down
Expand Up @@ -14,16 +14,17 @@

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @CsvFileSource} is an {@link ArgumentsSource} which is used to load
* comma-separated value (CSV) files from one or more classpath {@link #resources}
* or {@link #files}.
* {@code @CsvFileSource} is a {@linkplain Repeatable repeatable}
* {@link ArgumentsSource} which is used to load comma-separated value (CSV)
* files from one or more classpath {@link #resources} or {@link #files}.
*
* <p>The CSV records parsed from these resources and files will be provided as
* arguments to the annotated {@code @ParameterizedTest} method. Note that the
Expand Down Expand Up @@ -63,6 +64,7 @@
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(CsvFileSources.class)
@API(status = STABLE, since = "5.7")
@ArgumentsSource(CsvFileArgumentsProvider.class)
@SuppressWarnings("exports")
Expand Down
@@ -0,0 +1,46 @@
/*
* Copyright 2015-2024 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.params.provider;

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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @CsvFileSources} is a simple container for one or more
* {@link CsvFileSource} annotations.
*
* <p>Note, however, that use of the {@code @CsvFileSources} container is completely
* optional since {@code @CsvFileSource} is a {@linkplain java.lang.annotation.Repeatable
* repeatable} annotation.
*
* @since 5.11
* @see CsvFileSource
* @see java.lang.annotation.Repeatable
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.11")
public @interface CsvFileSources {

/**
* An array of one or more {@link CsvFileSource @CsvFileSource}
* annotations.
*/
CsvFileSource[] value();
}
Expand Up @@ -14,16 +14,18 @@

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @CsvSource} is an {@link ArgumentsSource} which reads comma-separated
* values (CSV) from one or more CSV records supplied via the {@link #value}
* attribute or {@link #textBlock} attribute.
* {@code @CsvSource} is a {@linkplain Repeatable repeatable}
* {@link ArgumentsSource} which reads comma-separated values (CSV) from one
* or more CSV records supplied via the {@link #value} attribute or
* {@link #textBlock} attribute.
*
* <p>The supplied values will be provided as arguments to the annotated
* {@code @ParameterizedTest} method.
Expand Down Expand Up @@ -64,6 +66,7 @@
*/
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(CsvSources.class)
@Documented
@API(status = STABLE, since = "5.7")
@ArgumentsSource(CsvArgumentsProvider.class)
Expand Down
@@ -0,0 +1,46 @@
/*
* Copyright 2015-2024 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.params.provider;

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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @CsvSources} is a simple container for one or more
* {@link CsvSource} annotations.
*
* <p>Note, however, that use of the {@code @CsvSources} container is completely
* optional since {@code @CsvSource} is a {@linkplain java.lang.annotation.Repeatable
* repeatable} annotation.
*
* @since 5.11
* @see CsvSource
* @see java.lang.annotation.Repeatable
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.11")
public @interface CsvSources {

/**
* An array of one or more {@link CsvSource @CsvSource}
* annotations.
*/
CsvSource[] value();
}
Expand Up @@ -16,6 +16,7 @@

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -29,8 +30,8 @@
import org.junit.platform.commons.util.Preconditions;

/**
* {@code @EnumSource} is an {@link ArgumentsSource} for constants of
* an {@link Enum}.
* {@code @EnumSource} is a {@linkplain Repeatable repeatable}
* {@link ArgumentsSource} for constants of an {@link Enum}.
*
* <p>The enum constants will be provided as arguments to the annotated
* {@code @ParameterizedTest} method.
Expand All @@ -49,6 +50,7 @@
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(EnumSources.class)
@API(status = STABLE, since = "5.7")
@ArgumentsSource(EnumArgumentsProvider.class)
@SuppressWarnings("exports")
Expand Down
@@ -0,0 +1,46 @@
/*
* Copyright 2015-2024 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.params.provider;

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

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.apiguardian.api.API;

/**
* {@code @EnumSources} is a simple container for one or more
* {@link EnumSource} annotations.
*
* <p>Note, however, that use of the {@code @EnumSources} container is completely
* optional since {@code @EnumSource} is a {@linkplain java.lang.annotation.Repeatable
* repeatable} annotation.
*
* @since 5.11
* @see EnumSource
* @see java.lang.annotation.Repeatable
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@API(status = STABLE, since = "5.11")
public @interface EnumSources {

/**
* An array of one or more {@link EnumSource @EnumSource}
* annotations.
*/
EnumSource[] value();
}
Expand Up @@ -14,6 +14,7 @@

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
Expand All @@ -22,10 +23,11 @@
import org.junit.jupiter.params.ParameterizedTest;

/**
* {@code @FieldSource} is an {@link ArgumentsSource} which provides access to
* values of {@linkplain #value() fields} of the class in which this annotation
* is declared or from static fields in external classes referenced by
* <em>fully qualified field name</em>.
* {@code @FieldSource} is a {@linkplain Repeatable repeatable}
* {@link ArgumentsSource} which provides access to values of
* {@linkplain #value() fields} of the class in which this annotation is declared
* or from static fields in external classes referenced by <em>fully qualified
* field name</em>.
*
* <p>Each field must be able to supply a <em>stream</em> of <em>arguments</em>,
* and each set of "arguments" within the "stream" will be provided as the physical
Expand Down Expand Up @@ -112,6 +114,7 @@
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(FieldSources.class)
@API(status = EXPERIMENTAL, since = "5.11")
@ArgumentsSource(FieldArgumentsProvider.class)
@SuppressWarnings("exports")
Expand Down