Skip to content

Commit 2886963

Browse files
Make parameterized ArgumentSource annotations repeatable
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.
1 parent e2a7cc0 commit 2886963

24 files changed

+638
-49
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.11.0-M2.adoc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ repository on GitHub.
5858
[[release-notes-5.11.0-M2-junit-jupiter-new-features-and-improvements]]
5959
==== New Features and Improvements
6060

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

6465
[[release-notes-5.11.0-M2-junit-vintage]]
6566
=== JUnit Vintage

documentation/src/docs/asciidoc/user-guide/writing-tests.adoc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,34 @@ If you wish to implement a custom `ArgumentsProvider` that also consumes an anno
19631963
(like built-in providers such as `{ValueArgumentsProvider}` or `{CsvArgumentsProvider}`),
19641964
you have the possibility to extend the `{AnnotationBasedArgumentsProvider}` class.
19651965

1966+
[[writing-tests-parameterized-repeatable-sources]]
1967+
===== Multiple sources using repeatable annotations
1968+
Repeatable annotations provide a convenient way to specify multiple sources from
1969+
different providers.
1970+
1971+
[source,java,indent=0]
1972+
----
1973+
include::{testDir}/example/ParameterizedTestDemo.java[tags=repeatable_annotations]
1974+
----
1975+
1976+
Following the above parameterized test, a test case will run for each argument:
1977+
1978+
----
1979+
[1] foo
1980+
[2] bar
1981+
----
1982+
1983+
The following annotations are repeatable:
1984+
1985+
* `@ValueSource`
1986+
* `@EnumSource`
1987+
* `@MethodSource`
1988+
* `@FieldSource`
1989+
* `@CsvSource`
1990+
* `@CsvFileSource`
1991+
* `@ArgumentsSource`
1992+
1993+
19661994
[[writing-tests-parameterized-tests-argument-conversion]]
19671995
==== Argument Conversion
19681996

documentation/src/test/java/example/ParameterizedTestDemo.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -542,4 +542,22 @@ static Stream<Arguments> namedArguments() {
542542
}
543543
// end::named_arguments[]
544544
// @formatter:on
545+
546+
// tag::repeatable_annotations[]
547+
@DisplayName("A parameterized test that makes use of repeatable annotations")
548+
@ParameterizedTest
549+
@MethodSource("someProvider")
550+
@MethodSource("otherProvider")
551+
void testWithRepeatedAnnotation(String argument) {
552+
assertNotNull(argument);
553+
}
554+
555+
static Stream<String> someProvider() {
556+
return Stream.of("foo");
557+
}
558+
559+
static Stream<String> otherProvider() {
560+
return Stream.of("bar");
561+
}
562+
// end::repeatable_annotations[]
545563
}

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/AnnotationBasedArgumentsProvider.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
1414

1515
import java.lang.annotation.Annotation;
16+
import java.util.ArrayList;
17+
import java.util.List;
1618
import java.util.stream.Stream;
1719

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

42-
private A annotation;
44+
private final List<A> annotations = new ArrayList<>();
4345

4446
@Override
4547
public final void accept(A annotation) {
4648
Preconditions.notNull(annotation, "annotation must not be null");
47-
this.annotation = annotation;
49+
annotations.add(annotation);
4850
}
4951

5052
@Override
5153
public final Stream<? extends Arguments> provideArguments(ExtensionContext context) {
52-
return provideArguments(context, this.annotation);
54+
return annotations.stream().flatMap(annotation -> provideArguments(context, annotation));
5355
}
5456

5557
/**

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvFileSource.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,17 @@
1414

1515
import java.lang.annotation.Documented;
1616
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Repeatable;
1718
import java.lang.annotation.Retention;
1819
import java.lang.annotation.RetentionPolicy;
1920
import java.lang.annotation.Target;
2021

2122
import org.apiguardian.api.API;
2223

2324
/**
24-
* {@code @CsvFileSource} is an {@link ArgumentsSource} which is used to load
25-
* comma-separated value (CSV) files from one or more classpath {@link #resources}
26-
* or {@link #files}.
25+
* {@code @CsvFileSource} is a {@linkplain Repeatable repeatable}
26+
* {@link ArgumentsSource} which is used to load comma-separated value (CSV)
27+
* files from one or more classpath {@link #resources} or {@link #files}.
2728
*
2829
* <p>The CSV records parsed from these resources and files will be provided as
2930
* arguments to the annotated {@code @ParameterizedTest} method. Note that the
@@ -63,6 +64,7 @@
6364
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
6465
@Retention(RetentionPolicy.RUNTIME)
6566
@Documented
67+
@Repeatable(CsvFileSources.class)
6668
@API(status = STABLE, since = "5.7")
6769
@ArgumentsSource(CsvFileArgumentsProvider.class)
6870
@SuppressWarnings("exports")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.params.provider;
12+
13+
import static org.apiguardian.api.API.Status.STABLE;
14+
15+
import java.lang.annotation.Documented;
16+
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Retention;
18+
import java.lang.annotation.RetentionPolicy;
19+
import java.lang.annotation.Target;
20+
21+
import org.apiguardian.api.API;
22+
23+
/**
24+
* {@code @CsvFileSources} is a simple container for one or more
25+
* {@link CsvFileSource} annotations.
26+
*
27+
* <p>Note, however, that use of the {@code @CsvFileSources} container is completely
28+
* optional since {@code @CsvFileSource} is a {@linkplain java.lang.annotation.Repeatable
29+
* repeatable} annotation.
30+
*
31+
* @since 5.11
32+
* @see CsvFileSource
33+
* @see java.lang.annotation.Repeatable
34+
*/
35+
@Target(ElementType.METHOD)
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Documented
38+
@API(status = STABLE, since = "5.11")
39+
public @interface CsvFileSources {
40+
41+
/**
42+
* An array of one or more {@link CsvFileSource @CsvFileSource}
43+
* annotations.
44+
*/
45+
CsvFileSource[] value();
46+
}

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/CsvSource.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414

1515
import java.lang.annotation.Documented;
1616
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Repeatable;
1718
import java.lang.annotation.Retention;
1819
import java.lang.annotation.RetentionPolicy;
1920
import java.lang.annotation.Target;
2021

2122
import org.apiguardian.api.API;
2223

2324
/**
24-
* {@code @CsvSource} is an {@link ArgumentsSource} which reads comma-separated
25-
* values (CSV) from one or more CSV records supplied via the {@link #value}
26-
* attribute or {@link #textBlock} attribute.
25+
* {@code @CsvSource} is a {@linkplain Repeatable repeatable}
26+
* {@link ArgumentsSource} which reads comma-separated values (CSV) from one
27+
* or more CSV records supplied via the {@link #value} attribute or
28+
* {@link #textBlock} attribute.
2729
*
2830
* <p>The supplied values will be provided as arguments to the annotated
2931
* {@code @ParameterizedTest} method.
@@ -64,6 +66,7 @@
6466
*/
6567
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
6668
@Retention(RetentionPolicy.RUNTIME)
69+
@Repeatable(CsvSources.class)
6770
@Documented
6871
@API(status = STABLE, since = "5.7")
6972
@ArgumentsSource(CsvArgumentsProvider.class)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.params.provider;
12+
13+
import static org.apiguardian.api.API.Status.STABLE;
14+
15+
import java.lang.annotation.Documented;
16+
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Retention;
18+
import java.lang.annotation.RetentionPolicy;
19+
import java.lang.annotation.Target;
20+
21+
import org.apiguardian.api.API;
22+
23+
/**
24+
* {@code @CsvSources} is a simple container for one or more
25+
* {@link CsvSource} annotations.
26+
*
27+
* <p>Note, however, that use of the {@code @CsvSources} container is completely
28+
* optional since {@code @CsvSource} is a {@linkplain java.lang.annotation.Repeatable
29+
* repeatable} annotation.
30+
*
31+
* @since 5.11
32+
* @see CsvSource
33+
* @see java.lang.annotation.Repeatable
34+
*/
35+
@Target(ElementType.METHOD)
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Documented
38+
@API(status = STABLE, since = "5.11")
39+
public @interface CsvSources {
40+
41+
/**
42+
* An array of one or more {@link CsvSource @CsvSource}
43+
* annotations.
44+
*/
45+
CsvSource[] value();
46+
}

junit-jupiter-params/src/main/java/org/junit/jupiter/params/provider/EnumSource.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import java.lang.annotation.Documented;
1818
import java.lang.annotation.ElementType;
19+
import java.lang.annotation.Repeatable;
1920
import java.lang.annotation.Retention;
2021
import java.lang.annotation.RetentionPolicy;
2122
import java.lang.annotation.Target;
@@ -29,8 +30,8 @@
2930
import org.junit.platform.commons.util.Preconditions;
3031

3132
/**
32-
* {@code @EnumSource} is an {@link ArgumentsSource} for constants of
33-
* an {@link Enum}.
33+
* {@code @EnumSource} is a {@linkplain Repeatable repeatable}
34+
* {@link ArgumentsSource} for constants of an {@link Enum}.
3435
*
3536
* <p>The enum constants will be provided as arguments to the annotated
3637
* {@code @ParameterizedTest} method.
@@ -49,6 +50,7 @@
4950
@Target({ ElementType.ANNOTATION_TYPE, ElementType.METHOD })
5051
@Retention(RetentionPolicy.RUNTIME)
5152
@Documented
53+
@Repeatable(EnumSources.class)
5254
@API(status = STABLE, since = "5.7")
5355
@ArgumentsSource(EnumArgumentsProvider.class)
5456
@SuppressWarnings("exports")
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2015-2024 the original author or authors.
3+
*
4+
* All rights reserved. This program and the accompanying materials are
5+
* made available under the terms of the Eclipse Public License v2.0 which
6+
* accompanies this distribution and is available at
7+
*
8+
* https://www.eclipse.org/legal/epl-v20.html
9+
*/
10+
11+
package org.junit.jupiter.params.provider;
12+
13+
import static org.apiguardian.api.API.Status.STABLE;
14+
15+
import java.lang.annotation.Documented;
16+
import java.lang.annotation.ElementType;
17+
import java.lang.annotation.Retention;
18+
import java.lang.annotation.RetentionPolicy;
19+
import java.lang.annotation.Target;
20+
21+
import org.apiguardian.api.API;
22+
23+
/**
24+
* {@code @EnumSources} is a simple container for one or more
25+
* {@link EnumSource} annotations.
26+
*
27+
* <p>Note, however, that use of the {@code @EnumSources} container is completely
28+
* optional since {@code @EnumSource} is a {@linkplain java.lang.annotation.Repeatable
29+
* repeatable} annotation.
30+
*
31+
* @since 5.11
32+
* @see EnumSource
33+
* @see java.lang.annotation.Repeatable
34+
*/
35+
@Target(ElementType.METHOD)
36+
@Retention(RetentionPolicy.RUNTIME)
37+
@Documented
38+
@API(status = STABLE, since = "5.11")
39+
public @interface EnumSources {
40+
41+
/**
42+
* An array of one or more {@link EnumSource @EnumSource}
43+
* annotations.
44+
*/
45+
EnumSource[] value();
46+
}

0 commit comments

Comments
 (0)