Skip to content

Commit e884aa6

Browse files
Support named sources for DynamicTest.stream(…) (#2548)
Introduction of `DynamicTest.stream(Iterator<? extends Named<T>>, ThrowingConsumer<? super T>)` and `DynamicTest.stream(Stream<? extends Named<T>>, ThrowingConsumer<? super T>)`. Both methods use each name-value pair provided by the first parameter as the display name and value for each generated dynamic test. Resolves #2375.
1 parent b2a9810 commit e884aa6

File tree

4 files changed

+128
-0
lines changed

4 files changed

+128
-0
lines changed

documentation/src/docs/asciidoc/release-notes/release-notes-5.8.0-M1.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ on GitHub.
8888
* In parameterized tests with `@MethodSource` or `@ArgumentSource`, arguments can now have
8989
optional names. When the argument is included in the display name of an iteration, this
9090
name will be used instead of the value.
91+
* `DynamicTests.stream()` can now consume `Named` input and will use each name-value
92+
pair as the display name and value for each generated dynamic test (see
93+
<<../user-guide/index.adoc#writing-tests-dynamic-tests-examples,User Guide>> for details).
94+
9195

9296
[[release-notes-5.8.0-M1-junit-vintage]]
9397
=== JUnit Vintage

documentation/src/test/java/example/DynamicTestsDemo.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333

3434
import org.junit.jupiter.api.DynamicNode;
3535
import org.junit.jupiter.api.DynamicTest;
36+
import org.junit.jupiter.api.Named;
3637
import org.junit.jupiter.api.Tag;
3738
import org.junit.jupiter.api.TestFactory;
3839
import org.junit.jupiter.api.function.ThrowingConsumer;
@@ -151,6 +152,23 @@ Stream<DynamicTest> dynamicTestsFromStreamFactoryMethod() {
151152
return DynamicTest.stream(inputStream, displayNameGenerator, testExecutor);
152153
}
153154

155+
@TestFactory
156+
Stream<DynamicTest> dynamicTestsFromStreamFactoryMethodWithNames() {
157+
// Stream of palindromes to check
158+
Stream<Named<String>> inputStream = Stream.of(
159+
Named.of("racecar is a palindrome", "racecar"),
160+
Named.of("radar is also a palindrome", "radar"),
161+
Named.of("mom also seems to be a palindrome", "mom"),
162+
Named.of("dad is yet another palindrome", "dad")
163+
);
164+
165+
// Executes tests based on the current input value.
166+
ThrowingConsumer<String> testExecutor = text -> assertTrue(isPalindrome(text));
167+
168+
// Returns a stream of dynamic tests.
169+
return DynamicTest.stream(inputStream, testExecutor);
170+
}
171+
154172
@TestFactory
155173
Stream<DynamicNode> dynamicTestsWithContainers() {
156174
return Stream.of("A", "B", "C")

junit-jupiter-api/src/main/java/org/junit/jupiter/api/DynamicTest.java

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,77 @@ public static <T> Stream<DynamicTest> stream(Stream<T> inputStream,
155155
.map(input -> dynamicTest(displayNameGenerator.apply(input), () -> testExecutor.accept(input)));
156156
}
157157

158+
/**
159+
* Generate a stream of dynamic tests based on the given generator and test
160+
* executor.
161+
*
162+
* <p>Use this method when the set of dynamic tests is nondeterministic in
163+
* nature or when the input comes from an existing {@link Iterator}. See
164+
* {@link #stream(Stream, ThrowingConsumer)} as an alternative.
165+
*
166+
* <p>The given {@code inputGenerator} is responsible for generating
167+
* input values and display names. A {@link DynamicTest} will be added to
168+
* the resulting stream for each dynamically generated input value,
169+
* using the given {@code testExecutor}.
170+
*
171+
* @param inputGenerator an {@code Iterator} with {@code Named} values
172+
* that serves as a dynamic <em>input generator</em>; never {@code null}
173+
* @param testExecutor a consumer that executes a test based on an input
174+
* value; never {@code null}
175+
* @param <T> the type of <em>input</em> generated by the {@code inputGenerator}
176+
* and used by the {@code testExecutor}
177+
* @return a stream of dynamic tests based on the given generator and
178+
* executor; never {@code null}
179+
* @since 5.8
180+
*
181+
* @see #dynamicTest(String, Executable)
182+
* @see #stream(Stream, ThrowingConsumer)
183+
* @see Named
184+
*/
185+
@API(status = MAINTAINED, since = "5.8")
186+
public static <T> Stream<DynamicTest> stream(Iterator<? extends Named<T>> inputGenerator,
187+
ThrowingConsumer<? super T> testExecutor) {
188+
Preconditions.notNull(inputGenerator, "inputGenerator must not be null");
189+
190+
return stream(StreamSupport.stream(spliteratorUnknownSize(inputGenerator, ORDERED), false), testExecutor);
191+
}
192+
193+
/**
194+
* Generate a stream of dynamic tests based on the given input stream and
195+
* test executor.
196+
*
197+
* <p>Use this method when the set of dynamic tests is nondeterministic in
198+
* nature or when the input comes from an existing {@link Stream}. See
199+
* {@link #stream(Iterator, ThrowingConsumer)} as an alternative.
200+
*
201+
* <p>The given {@code inputStream} is responsible for supplying input values
202+
* and display names. A {@link DynamicTest} will be added to the resulting stream for
203+
* each dynamically supplied input value, using the given {@code testExecutor}.
204+
*
205+
* @param inputStream a {@code Stream} that supplies dynamic {@code Named}
206+
* input values; never {@code null}
207+
* @param testExecutor a consumer that executes a test based on an input
208+
* value; never {@code null}
209+
* @param <T> the type of <em>input</em> supplied by the {@code inputStream}
210+
* and used by the {@code displayNameGenerator} and {@code testExecutor}
211+
* @return a stream of dynamic tests based on the given generator and
212+
* executor; never {@code null}
213+
* @since 5.8
214+
*
215+
* @see #dynamicTest(String, Executable)
216+
* @see #stream(Iterator, ThrowingConsumer)
217+
* @see Named
218+
*/
219+
@API(status = MAINTAINED, since = "5.8")
220+
public static <T> Stream<DynamicTest> stream(Stream<? extends Named<T>> inputStream,
221+
ThrowingConsumer<? super T> testExecutor) {
222+
Preconditions.notNull(inputStream, "inputStream must not be null");
223+
Preconditions.notNull(testExecutor, "testExecutor must not be null");
224+
225+
return inputStream //
226+
.map(input -> dynamicTest(input.getName(), () -> testExecutor.accept(input.getPayload())));
227+
}
228+
158229
private final Executable executable;
159230

160231
private DynamicTest(String displayName, URI testSourceUri, Executable executable) {

junit-jupiter-engine/src/test/java/org/junit/jupiter/api/DynamicTestTests.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,26 @@ void streamFromIteratorPreconditions() {
7070
() -> DynamicTest.stream(emptyIterator(), displayNameGenerator, null));
7171
}
7272

73+
@Test
74+
void streamFromStreamWithNamesPreconditions() {
75+
ThrowingConsumer<Object> testExecutor = input -> {
76+
};
77+
78+
assertThrows(PreconditionViolationException.class,
79+
() -> DynamicTest.stream((Stream<? extends Named<Object>>) null, testExecutor));
80+
assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(Stream.empty(), null));
81+
}
82+
83+
@Test
84+
void streamFromIteratorWithNamesPreconditions() {
85+
ThrowingConsumer<Object> testExecutor = input -> {
86+
};
87+
88+
assertThrows(PreconditionViolationException.class,
89+
() -> DynamicTest.stream((Iterator<? extends Named<Object>>) null, testExecutor));
90+
assertThrows(PreconditionViolationException.class, () -> DynamicTest.stream(emptyIterator(), null));
91+
}
92+
7393
@Test
7494
void streamFromStream() throws Throwable {
7595
Stream<DynamicTest> stream = DynamicTest.stream(Stream.of("foo", "bar", "baz"), String::toUpperCase,
@@ -84,6 +104,21 @@ void streamFromIterator() throws Throwable {
84104
assertStream(stream);
85105
}
86106

107+
@Test
108+
void streamFromStreamWithNames() throws Throwable {
109+
Stream<DynamicTest> stream = DynamicTest.stream(
110+
Stream.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")), this::throwingConsumer);
111+
assertStream(stream);
112+
}
113+
114+
@Test
115+
void streamFromIteratorWithNames() throws Throwable {
116+
Stream<DynamicTest> stream = DynamicTest.stream(
117+
List.of(Named.of("FOO", "foo"), Named.of("BAR", "bar"), Named.of("BAZ", "baz")).iterator(),
118+
this::throwingConsumer);
119+
assertStream(stream);
120+
}
121+
87122
private void assertStream(Stream<DynamicTest> stream) throws Throwable {
88123
List<DynamicTest> dynamicTests = stream.collect(Collectors.toList());
89124

0 commit comments

Comments
 (0)