Skip to content

Commit ade732d

Browse files
committed
Merge branch 'issues/1771-testkit-loose-event-matching'
2 parents f1ed410 + 992b1fb commit ade732d

File tree

4 files changed

+424
-1
lines changed

4 files changed

+424
-1
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,13 @@ on GitHub.
3636
execution of a submitted test via the returned `Future`.
3737
* Add `EngineExecutionListener.NOOP` and change all declared methods to have empty default
3838
implementations.
39-
39+
* The TestKit now allows to match conditions with events loosely, i.e. an incomplete match
40+
with or without a fixed order.
41+
* Add `Node.DynamicTestExecutor#execute(TestDescriptor, EngineExecutionListener)` for
42+
engines that wish to pass a custom `EngineExecutionListener` and cancel or wait for the
43+
execution of a submitted test via the returned `Future`.
44+
* Add `EngineExecutionListener.NOOP` and change all declared methods to have empty default
45+
implementations.
4046

4147
[[release-notes-5.7.0-M1-junit-jupiter]]
4248
=== JUnit Jupiter

documentation/src/docs/asciidoc/user-guide/testkit.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ methods are executed, which in turn allows our `verifyAllJupiterEvents()` test t
132132
reliable.
133133
====
134134

135+
If you want to do a _partial_ match _with_ or _without_ ordering requirements, you can use
136+
the methods `assertEventsMatchLooselyInOrder()` and `assertEventsMatchLoosely()`,
137+
respectively.
138+
135139
[source,java,indent=0]
136140
----
137141
include::{testDir}/example/testkit/EngineTestKitAllEventsDemo.java[tags=user_guide]

junit-platform-testkit/src/main/java/org/junit/platform/testkit/engine/Events.java

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
package org.junit.platform.testkit.engine;
1212

13+
import static java.util.Collections.sort;
1314
import static java.util.function.Predicate.isEqual;
1415
import static java.util.stream.Collectors.toList;
1516
import static org.apiguardian.api.API.Status.EXPERIMENTAL;
@@ -20,8 +21,12 @@
2021
import java.io.OutputStream;
2122
import java.io.PrintWriter;
2223
import java.io.Writer;
24+
import java.util.ArrayList;
25+
import java.util.Arrays;
2326
import java.util.Collections;
2427
import java.util.List;
28+
import java.util.Objects;
29+
import java.util.Optional;
2530
import java.util.function.Consumer;
2631
import java.util.function.Function;
2732
import java.util.function.Predicate;
@@ -36,6 +41,7 @@
3641
import org.junit.platform.commons.util.Preconditions;
3742
import org.junit.platform.engine.TestExecutionResult;
3843
import org.junit.platform.engine.TestExecutionResult.Status;
44+
import org.opentest4j.AssertionFailedError;
3945

4046
/**
4147
* {@code Events} is a facade that provides a fluent API for working with
@@ -259,6 +265,67 @@ public final void assertEventsMatchExactly(Condition<? super Event>... condition
259265
assertEventsMatchExactly(this.events, conditions);
260266
}
261267

268+
/**
269+
* Assert that all provided conditions are matched by an {@linkplain Event event}
270+
* contained in this {@code Events} object regardless of order.
271+
* Note that this method does a partial match, i.e. some events may not match any
272+
* of the provided conditions.
273+
*
274+
* <p>Conditions can be imported statically from {@link EventConditions}
275+
* and {@link TestExecutionResultConditions}.
276+
*
277+
* <h4>Example</h4>
278+
*
279+
* <pre class="code">
280+
* executionResults.testEvents().assertEventsMatchLoosely(
281+
* event(test("exampleTestMethod"), started()),
282+
* event(test("exampleTestMethod"), finishedSuccessfully())
283+
* );
284+
* </pre>
285+
*
286+
* @param conditions the conditions to match against; never {@code null}
287+
* @see EventConditions
288+
* @see TestExecutionResultConditions
289+
*/
290+
@SafeVarargs
291+
@SuppressWarnings("varargs")
292+
public final void assertEventsMatchLoosely(Condition<? super Event>... conditions) {
293+
Preconditions.notNull(conditions, "conditions must not be null");
294+
Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements");
295+
assertEventsMatchLoosely(this.events, conditions);
296+
}
297+
298+
/**
299+
* Assert that all provided conditions are matched by an {@linkplain Event event}
300+
* contained in this {@code Events} object.
301+
* Note that this method does a partial match, i.e. some events may not match any
302+
* of the provided conditions.
303+
* However, the conditions provided must be in the correct order.
304+
*
305+
* <p>Conditions can be imported statically from {@link EventConditions}
306+
* and {@link TestExecutionResultConditions}.
307+
*
308+
* <h4>Example</h4>
309+
*
310+
* <pre class="code">
311+
* executionResults.testEvents().assertEventsMatchLooselyInOrder(
312+
* event(test("exampleTestMethod"), started()),
313+
* event(test("exampleTestMethod"), finishedSuccessfully())
314+
* );
315+
* </pre>
316+
*
317+
* @param conditions the conditions to match against; never {@code null}
318+
* @see EventConditions
319+
* @see TestExecutionResultConditions
320+
*/
321+
@SafeVarargs
322+
@SuppressWarnings("varargs")
323+
public final void assertEventsMatchLooselyInOrder(Condition<? super Event>... conditions) {
324+
Preconditions.notNull(conditions, "conditions must not be null");
325+
Preconditions.containsNoNullElements(conditions, "conditions must not contain null elements");
326+
assertEventsMatchLooselyInOrder(this.events, conditions);
327+
}
328+
262329
/**
263330
* Shortcut for {@code org.assertj.core.api.Assertions.assertThat(events.list())}.
264331
*
@@ -336,4 +403,63 @@ private static void assertEventsMatchExactly(List<Event> events, Condition<? sup
336403
softly.assertAll();
337404
}
338405

406+
@SafeVarargs
407+
private static void assertEventsMatchLoosely(List<Event> events, Condition<? super Event>... conditions) {
408+
SoftAssertions softly = new SoftAssertions();
409+
for (Condition<? super Event> condition : conditions) {
410+
checkCondition(events, softly, condition);
411+
}
412+
softly.assertAll();
413+
}
414+
415+
@SafeVarargs
416+
@SuppressWarnings("varargs")
417+
private static void assertEventsMatchLooselyInOrder(List<Event> events, Condition<? super Event>... conditions) {
418+
Assertions.assertThat(conditions).hasSizeLessThanOrEqualTo(events.size());
419+
SoftAssertions softly = new SoftAssertions();
420+
421+
// @formatter:off
422+
List<Integer> indices = Arrays.stream(conditions)
423+
.map(condition -> findEvent(events, softly, condition))
424+
.filter(Objects::nonNull)
425+
.map(events::indexOf)
426+
.collect(toList());
427+
// @formatter:on
428+
429+
if (isNotInIncreasingOrder(indices)) {
430+
throw new AssertionFailedError("Conditions are not in the correct order.");
431+
}
432+
433+
softly.assertAll();
434+
}
435+
436+
private static boolean isNotInIncreasingOrder(List<Integer> indices) {
437+
List<Integer> copy = new ArrayList<>(indices);
438+
sort(copy);
439+
440+
return !indices.equals(copy);
441+
}
442+
443+
private static void checkCondition(List<Event> events, SoftAssertions softly, Condition<? super Event> condition) {
444+
boolean matches = events.stream().anyMatch(condition::matches);
445+
446+
if (!matches) {
447+
softly.fail("Condition did not match any event: " + condition);
448+
}
449+
}
450+
451+
private static Event findEvent(List<Event> events, SoftAssertions softly, Condition<? super Event> condition) {
452+
// @formatter:off
453+
Optional<Event> matchedEvent = events.stream()
454+
.filter(condition::matches)
455+
.findFirst();
456+
// @formatter:on
457+
458+
if (!matchedEvent.isPresent()) {
459+
softly.fail("Condition did not match any event: " + condition);
460+
}
461+
462+
return matchedEvent.orElse(null);
463+
}
464+
339465
}

0 commit comments

Comments
 (0)