Skip to content

Commit

Permalink
Make parameterized ArgumentSource annotations repeatable
Browse files Browse the repository at this point in the history
This commit makes every `@..Source` annotations that makes use of 
`AnnotationBasedArgumentsProvider` repeatable by adapting to accept 
multiple annotations while aggregating the result of each annotation 
argument.

This change does not affect annotations related to null and/or empty 
sources: `@NullSource`, `@EmptySource` and `@NullAndEmptySource` since 
they have predefined/hardcoded arguments relevant to null and empty 
values.

Resolves #3736.
  • Loading branch information
madalingiurca committed May 14, 2024
1 parent e2a7cc0 commit 2886963
Show file tree
Hide file tree
Showing 24 changed files with 638 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ 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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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[]
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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();
}
Original file line number Diff line number Diff line change
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
Loading

0 comments on commit 2886963

Please sign in to comment.