From bb466f99e953363499a12dba25191e4b8bbf0742 Mon Sep 17 00:00:00 2001 From: Oliver Drotbohm Date: Thu, 23 Feb 2023 22:14:20 +0100 Subject: [PATCH] GH-136 - API polishing for Scenario. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Move the registration of cleanup callbacks to the intermediate When type to avoid overloads of Scenario.stimulate(…). Also, to prevent multiple lambda-style parameters for ….stimulate(…) as they don't distinguish nice on the declaration side. Removed the varargs from ….publish(…) methods and wait for the actual request to add support for preparing, in transaction callbacks (potentially to be introduced as ….prepare(…) method). The default acceptance criteria for state change expectations now also considers a boolean true as concluding method result. --- .../modulith/test/Scenario.java | 149 +++++++----------- .../modulith/test/ScenarioUnitTests.java | 16 +- 2 files changed, 69 insertions(+), 96 deletions(-) diff --git a/spring-modulith-test/src/main/java/org/springframework/modulith/test/Scenario.java b/spring-modulith-test/src/main/java/org/springframework/modulith/test/Scenario.java index 94d672d8..bca72d62 100644 --- a/spring-modulith-test/src/main/java/org/springframework/modulith/test/Scenario.java +++ b/spring-modulith-test/src/main/java/org/springframework/modulith/test/Scenario.java @@ -16,7 +16,6 @@ package org.springframework.modulith.test; import java.time.Duration; -import java.util.Arrays; import java.util.concurrent.Callable; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -55,6 +54,19 @@ import com.tngtech.archunit.thirdparty.com.google.common.base.Optional; @API(status = Status.EXPERIMENTAL) public class Scenario { + private static final Predicate DEFAULT_ACCEPTANCE = it -> { + + if (it instanceof Optional o) { + return o.isPresent(); + } + + if (it instanceof Boolean b) { + return b; + } + + return it != null; + }; + private final TransactionOperations transactionOperations; private final ApplicationEventPublisher publisher; private final AssertablePublishedEvents events; @@ -86,9 +98,9 @@ public class Scenario { * @param event must not be {@literal null}. * @return will never be {@literal null}. */ - public When publish(Object... event) { + public When publish(Object event) { return stimulate((tx, e) -> { - tx.executeWithoutResult(__ -> Arrays.stream(event).forEach(e::publishEvent)); + tx.executeWithoutResult(__ -> e.publishEvent(event)); }); } @@ -99,11 +111,10 @@ public class Scenario { * @param events must not be {@literal null}. * @return will never be {@literal null}. */ - @SuppressWarnings("unchecked") - public When publish(Supplier... events) { + public When publish(Supplier event) { return stimulate((tx, e) -> { - tx.executeWithoutResult(__ -> Arrays.stream(events).map(Supplier::get).forEach(e::publishEvent)); + tx.executeWithoutResult(__ -> e.publishEvent(event.get())); }); } @@ -120,7 +131,7 @@ public class Scenario { return stimulate(() -> { runnable.run(); return null; - }, __ -> {}); + }); } /** @@ -134,24 +145,7 @@ public class Scenario { * @see EventResult#toArriveAndVerify(Consumer) */ public When stimulate(Supplier supplier) { - return stimulate(supplier, __ -> {}); - } - - /** - * Stimulates the system using the given {@link Supplier}, keeping the supplied value around for later verification - * but also registers a cleanup callback to make sure potential state changes are rolled back for the test execution. - * - * @param the type of the value returned by the stimulus. - * @param supplier must not be {@literal null}. - * @param cleanupCallback must not be {@literal null}. - * @return will never be {@literal null}. - */ - public When stimulate(Supplier supplier, Consumer cleanupCallback) { - - Assert.notNull(supplier, "Supplier must not be null!"); - Assert.notNull(cleanupCallback, "Cleanup callback must not be null!"); - - return stimulate((tx) -> tx.execute(status -> supplier.get()), cleanupCallback); + return stimulate(__ -> supplier.get()); } /** @@ -163,25 +157,9 @@ public class Scenario { * @return will never be {@literal null}. */ public When stimulate(Function function) { - return stimulate(function, __ -> {}); - } - - /** - * Stimulates the system using the given function providing access to the {@link TransactionOperations} and keeping - * the supplied value around for later verification but also registers a cleanup callback to make sure potential state - * changes are rolled back for the test execution. - * - * @param the type of the value returned by the stimulus. - * @param function must not be {@literal null}. - * @param cleanupCallback must not be {@literal null}. - * @return will never be {@literal null}. - */ - public When stimulate(Function function, Consumer cleanupCallback) { - - Assert.notNull(function, "Function must not be null!"); - Assert.notNull(cleanupCallback, "Cleanup callback must not be null!"); - - return stimulate((tx, events) -> function.apply(tx), cleanupCallback); + return stimulate((tx, __) -> { + return function.apply(tx); + }); } /** @@ -195,24 +173,10 @@ public class Scenario { Assert.notNull(stimulus, "Stimulus must not be null!"); - return stimulate(stimulus, () -> {}); - } - - /** - * @param stimulus must not be {@literal null}. - * @param cleanupCallback must not be {@literal null}. - * @return will never be {@literal null}. - */ - public When stimulate(BiConsumer stimulus, - Runnable cleanupCallback) { - - Assert.notNull(stimulus, "Stimulus must not be null!"); - Assert.notNull(cleanupCallback, "Cleanup callback must not be null!"); - return stimulate((tx, e) -> { stimulus.accept(tx, e); return (Void) null; - }, __ -> cleanupCallback.run()); + }); } /** @@ -227,26 +191,7 @@ public class Scenario { Assert.notNull(stimulus, "Stimulus must not be null!"); - return stimulate(stimulus, __ -> {}); - } - - /** - * Stimulate the system using the given {@link TransactionOperations} and {@link ApplicationEventPublisher} (usually a - * method on some application service or event publication is triggered), produce a result and a callback to clean up - * after the verification has completed. - * - * @param the type of the result. - * @param stimulus must not be {@literal null}. - * @param cleanupCallback must not be {@literal null}. - * @return will never be {@literal null}. - */ - public When stimulate(BiFunction stimulus, - Consumer cleanupCallback) { - - Assert.notNull(stimulus, "Stimulus must not be null!"); - Assert.notNull(cleanupCallback, "Cleanup callback must not be null!"); - - return new When<>(stimulus, cleanupCallback, Function.identity()); + return new When<>(stimulus, __ -> {}, Function.identity()); } public class When { @@ -267,6 +212,34 @@ public class Scenario { this.customizer = customizer; } + /** + * Registers the given {@link Runnable} as cleanup callback to always run after completion of the {@link Scenario}, + * no matter the outcome of its execution (error or success). + * + * @param runnable must not be {@literal null}. + * @return will never be {@literal null}. + */ + public When andCleanup(Runnable runnable) { + + Assert.notNull(runnable, "Cleanup callback must not be null!"); + + return andCleanup(__ -> runnable.run()); + } + + /** + * Registers the given {@link Consumer} as cleanup callback to always run after completion of the {@link Scenario}, + * no matter the outcome of its execution (error or success). + * + * @param consumer must not be {@literal null}. + * @return will never be {@literal null}. + */ + public When andCleanup(Consumer consumer) { + + Assert.notNull(consumer, "Cleanup callback must not be null!"); + + return new When<>(stimulus, consumer, customizer); + } + // Customize /** @@ -319,7 +292,7 @@ public class Scenario { * @see #andWaitForStateChange(Supplier) */ public StateChangeResult forStateChange(Supplier supplier) { - return andWaitForStateChange(supplier); + return forStateChange(supplier, DEFAULT_ACCEPTANCE); } /** @@ -347,8 +320,10 @@ public class Scenario { } /** - * Expects a particular state change on the module to produce a result and uses the given {@link Predicate} to - * determine whether the value is conclusive. + * Expects a particular state change on the module to produce a result. By default, a non-{@literal null} value + * would indicate success, except for {@link java.util.Optional}s, in which case we'd check for the presence of a + * value and {@code booleans}, for which we accept {@literal true} as conclusive signal. For more control about the + * result matching, use {@link #andWaitForStateChange(Supplier, Predicate)}. * * @param the type of the result. * @param supplier must not be {@literal null}. @@ -356,16 +331,12 @@ public class Scenario { * @see #forStateChange(Supplier) */ public StateChangeResult andWaitForStateChange(Supplier supplier) { - - Predicate acceptanceCriteria = it -> it instanceof Optional o ? o.isPresent() : it != null; - - return andWaitForStateChange(supplier, acceptanceCriteria); + return andWaitForStateChange(supplier, DEFAULT_ACCEPTANCE); } /** - * Expects a particular state change on the module to produce a result. By default, a non-{@literal null} value - * would indicate success, except for {@link java.util.Optional}s, in which case we'd check for the presence of a - * value. For more control about the result matching, use {@link #andWaitForStateChange(Supplier, Predicate)} + * Expects a particular state change on the module to produce a result and uses the given {@link Predicate} to + * determine whether the value is conclusive. * * @param the type of the result for the state change * @param supplier must not be {@literal null}. diff --git a/spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioUnitTests.java b/spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioUnitTests.java index 794434a9..1c696699 100644 --- a/spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioUnitTests.java +++ b/spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioUnitTests.java @@ -83,7 +83,7 @@ class ScenarioUnitTests { @Test // GH-136 void matchesEventsWithPredicate() throws Throwable { - Consumer consumer = it -> publishObject(it) + Consumer consumer = it -> it.publish(() -> 41) .forEventOfType(SomeEvent.class) .matching(__ -> __.payload().startsWith("foo")) .toArrive(); @@ -194,7 +194,8 @@ class ScenarioUnitTests { BiConsumer verification = mock(BiConsumer.class); Consumer cleanupCallback = mock(Consumer.class); - Consumer consumer = it -> it.stimulate(() -> 41, cleanupCallback) + Consumer consumer = it -> it.stimulate(() -> 41) + .andCleanup(cleanupCallback) .forEventOfType(String.class) .toArriveAndVerify(verification); @@ -211,9 +212,10 @@ class ScenarioUnitTests { void triggersCleanupOnFailure() { BiConsumer verification = mock(BiConsumer.class); - Consumer cleanupCallback = mock(Consumer.class); + Runnable cleanupCallback = mock(Runnable.class); - Consumer consumer = it -> it.stimulate((tx) -> 41, cleanupCallback) + Consumer consumer = it -> it.stimulate((tx) -> 41) + .andCleanup(cleanupCallback) .andWaitAtMost(WAIT_TIME) .forEventOfType(String.class) .toArriveAndVerify(verification); @@ -223,7 +225,7 @@ class ScenarioUnitTests { .expectFailure(); verify(verification, never()).accept(any(), any()); - verify(cleanupCallback).accept(41); + verify(cleanupCallback).run(); } @Test // GH-136 @@ -266,8 +268,8 @@ class ScenarioUnitTests { Consumer verification = mock(Consumer.class); - Consumer consumer = it -> it.stimulate(() -> 41) - .andWaitForStateChange(delayed("Foo")) + Consumer consumer = it -> it.stimulate(() -> {}) + .forStateChange(delayed("Foo")) .andVerify(verification); givenAScenario(consumer)