Skip to content

Commit bb466f9

Browse files
committed
GH-136 - API polishing for Scenario.
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.
1 parent 50d8659 commit bb466f9

File tree

2 files changed

+69
-96
lines changed

2 files changed

+69
-96
lines changed

spring-modulith-test/src/main/java/org/springframework/modulith/test/Scenario.java

+60-89
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
package org.springframework.modulith.test;
1717

1818
import java.time.Duration;
19-
import java.util.Arrays;
2019
import java.util.concurrent.Callable;
2120
import java.util.function.BiConsumer;
2221
import java.util.function.BiFunction;
@@ -55,6 +54,19 @@
5554
@API(status = Status.EXPERIMENTAL)
5655
public class Scenario {
5756

57+
private static final Predicate<Object> DEFAULT_ACCEPTANCE = it -> {
58+
59+
if (it instanceof Optional<?> o) {
60+
return o.isPresent();
61+
}
62+
63+
if (it instanceof Boolean b) {
64+
return b;
65+
}
66+
67+
return it != null;
68+
};
69+
5870
private final TransactionOperations transactionOperations;
5971
private final ApplicationEventPublisher publisher;
6072
private final AssertablePublishedEvents events;
@@ -86,9 +98,9 @@ public class Scenario {
8698
* @param event must not be {@literal null}.
8799
* @return will never be {@literal null}.
88100
*/
89-
public When<Void> publish(Object... event) {
101+
public When<Void> publish(Object event) {
90102
return stimulate((tx, e) -> {
91-
tx.executeWithoutResult(__ -> Arrays.stream(event).forEach(e::publishEvent));
103+
tx.executeWithoutResult(__ -> e.publishEvent(event));
92104
});
93105
}
94106

@@ -99,11 +111,10 @@ public When<Void> publish(Object... event) {
99111
* @param events must not be {@literal null}.
100112
* @return will never be {@literal null}.
101113
*/
102-
@SuppressWarnings("unchecked")
103-
public When<Void> publish(Supplier<Object>... events) {
114+
public When<Void> publish(Supplier<Object> event) {
104115

105116
return stimulate((tx, e) -> {
106-
tx.executeWithoutResult(__ -> Arrays.stream(events).map(Supplier::get).forEach(e::publishEvent));
117+
tx.executeWithoutResult(__ -> e.publishEvent(event.get()));
107118
});
108119
}
109120

@@ -120,7 +131,7 @@ public When<Void> stimulate(Runnable runnable) {
120131
return stimulate(() -> {
121132
runnable.run();
122133
return null;
123-
}, __ -> {});
134+
});
124135
}
125136

126137
/**
@@ -134,24 +145,7 @@ public When<Void> stimulate(Runnable runnable) {
134145
* @see EventResult#toArriveAndVerify(Consumer)
135146
*/
136147
public <S> When<S> stimulate(Supplier<S> supplier) {
137-
return stimulate(supplier, __ -> {});
138-
}
139-
140-
/**
141-
* Stimulates the system using the given {@link Supplier}, keeping the supplied value around for later verification
142-
* but also registers a cleanup callback to make sure potential state changes are rolled back for the test execution.
143-
*
144-
* @param <S> the type of the value returned by the stimulus.
145-
* @param supplier must not be {@literal null}.
146-
* @param cleanupCallback must not be {@literal null}.
147-
* @return will never be {@literal null}.
148-
*/
149-
public <S> When<S> stimulate(Supplier<S> supplier, Consumer<S> cleanupCallback) {
150-
151-
Assert.notNull(supplier, "Supplier must not be null!");
152-
Assert.notNull(cleanupCallback, "Cleanup callback must not be null!");
153-
154-
return stimulate((tx) -> tx.execute(status -> supplier.get()), cleanupCallback);
148+
return stimulate(__ -> supplier.get());
155149
}
156150

157151
/**
@@ -163,25 +157,9 @@ public <S> When<S> stimulate(Supplier<S> supplier, Consumer<S> cleanupCallback)
163157
* @return will never be {@literal null}.
164158
*/
165159
public <S> When<S> stimulate(Function<TransactionOperations, S> function) {
166-
return stimulate(function, __ -> {});
167-
}
168-
169-
/**
170-
* Stimulates the system using the given function providing access to the {@link TransactionOperations} and keeping
171-
* the supplied value around for later verification but also registers a cleanup callback to make sure potential state
172-
* changes are rolled back for the test execution.
173-
*
174-
* @param <S> the type of the value returned by the stimulus.
175-
* @param function must not be {@literal null}.
176-
* @param cleanupCallback must not be {@literal null}.
177-
* @return will never be {@literal null}.
178-
*/
179-
public <S> When<S> stimulate(Function<TransactionOperations, S> function, Consumer<S> cleanupCallback) {
180-
181-
Assert.notNull(function, "Function must not be null!");
182-
Assert.notNull(cleanupCallback, "Cleanup callback must not be null!");
183-
184-
return stimulate((tx, events) -> function.apply(tx), cleanupCallback);
160+
return stimulate((tx, __) -> {
161+
return function.apply(tx);
162+
});
185163
}
186164

187165
/**
@@ -195,24 +173,10 @@ public When<Void> stimulate(BiConsumer<TransactionOperations, ApplicationEventPu
195173

196174
Assert.notNull(stimulus, "Stimulus must not be null!");
197175

198-
return stimulate(stimulus, () -> {});
199-
}
200-
201-
/**
202-
* @param stimulus must not be {@literal null}.
203-
* @param cleanupCallback must not be {@literal null}.
204-
* @return will never be {@literal null}.
205-
*/
206-
public When<Void> stimulate(BiConsumer<TransactionOperations, ApplicationEventPublisher> stimulus,
207-
Runnable cleanupCallback) {
208-
209-
Assert.notNull(stimulus, "Stimulus must not be null!");
210-
Assert.notNull(cleanupCallback, "Cleanup callback must not be null!");
211-
212176
return stimulate((tx, e) -> {
213177
stimulus.accept(tx, e);
214178
return (Void) null;
215-
}, __ -> cleanupCallback.run());
179+
});
216180
}
217181

218182
/**
@@ -227,26 +191,7 @@ public <S> When<S> stimulate(BiFunction<TransactionOperations, ApplicationEventP
227191

228192
Assert.notNull(stimulus, "Stimulus must not be null!");
229193

230-
return stimulate(stimulus, __ -> {});
231-
}
232-
233-
/**
234-
* Stimulate the system using the given {@link TransactionOperations} and {@link ApplicationEventPublisher} (usually a
235-
* method on some application service or event publication is triggered), produce a result and a callback to clean up
236-
* after the verification has completed.
237-
*
238-
* @param <S> the type of the result.
239-
* @param stimulus must not be {@literal null}.
240-
* @param cleanupCallback must not be {@literal null}.
241-
* @return will never be {@literal null}.
242-
*/
243-
public <S> When<S> stimulate(BiFunction<TransactionOperations, ApplicationEventPublisher, S> stimulus,
244-
Consumer<S> cleanupCallback) {
245-
246-
Assert.notNull(stimulus, "Stimulus must not be null!");
247-
Assert.notNull(cleanupCallback, "Cleanup callback must not be null!");
248-
249-
return new When<>(stimulus, cleanupCallback, Function.identity());
194+
return new When<>(stimulus, __ -> {}, Function.identity());
250195
}
251196

252197
public class When<T> {
@@ -267,6 +212,34 @@ public class When<T> {
267212
this.customizer = customizer;
268213
}
269214

215+
/**
216+
* Registers the given {@link Runnable} as cleanup callback to always run after completion of the {@link Scenario},
217+
* no matter the outcome of its execution (error or success).
218+
*
219+
* @param runnable must not be {@literal null}.
220+
* @return will never be {@literal null}.
221+
*/
222+
public When<T> andCleanup(Runnable runnable) {
223+
224+
Assert.notNull(runnable, "Cleanup callback must not be null!");
225+
226+
return andCleanup(__ -> runnable.run());
227+
}
228+
229+
/**
230+
* Registers the given {@link Consumer} as cleanup callback to always run after completion of the {@link Scenario},
231+
* no matter the outcome of its execution (error or success).
232+
*
233+
* @param consumer must not be {@literal null}.
234+
* @return will never be {@literal null}.
235+
*/
236+
public When<T> andCleanup(Consumer<T> consumer) {
237+
238+
Assert.notNull(consumer, "Cleanup callback must not be null!");
239+
240+
return new When<>(stimulus, consumer, customizer);
241+
}
242+
270243
// Customize
271244

272245
/**
@@ -319,7 +292,7 @@ public <E> EventResult<E> forEventOfType(Class<E> type) {
319292
* @see #andWaitForStateChange(Supplier)
320293
*/
321294
public <S> StateChangeResult<S> forStateChange(Supplier<S> supplier) {
322-
return andWaitForStateChange(supplier);
295+
return forStateChange(supplier, DEFAULT_ACCEPTANCE);
323296
}
324297

325298
/**
@@ -347,25 +320,23 @@ public <E> EventResult<E> andWaitForEventOfType(Class<E> type) {
347320
}
348321

349322
/**
350-
* Expects a particular state change on the module to produce a result and uses the given {@link Predicate} to
351-
* determine whether the value is conclusive.
323+
* Expects a particular state change on the module to produce a result. By default, a non-{@literal null} value
324+
* would indicate success, except for {@link java.util.Optional}s, in which case we'd check for the presence of a
325+
* value and {@code booleans}, for which we accept {@literal true} as conclusive signal. For more control about the
326+
* result matching, use {@link #andWaitForStateChange(Supplier, Predicate)}.
352327
*
353328
* @param <S> the type of the result.
354329
* @param supplier must not be {@literal null}.
355330
* @return will never be {@literal null}.
356331
* @see #forStateChange(Supplier)
357332
*/
358333
public <S> StateChangeResult<S> andWaitForStateChange(Supplier<S> supplier) {
359-
360-
Predicate<S> acceptanceCriteria = it -> it instanceof Optional<?> o ? o.isPresent() : it != null;
361-
362-
return andWaitForStateChange(supplier, acceptanceCriteria);
334+
return andWaitForStateChange(supplier, DEFAULT_ACCEPTANCE);
363335
}
364336

365337
/**
366-
* Expects a particular state change on the module to produce a result. By default, a non-{@literal null} value
367-
* would indicate success, except for {@link java.util.Optional}s, in which case we'd check for the presence of a
368-
* value. For more control about the result matching, use {@link #andWaitForStateChange(Supplier, Predicate)}
338+
* Expects a particular state change on the module to produce a result and uses the given {@link Predicate} to
339+
* determine whether the value is conclusive.
369340
*
370341
* @param <S> the type of the result for the state change
371342
* @param supplier must not be {@literal null}.

spring-modulith-test/src/test/java/org/springframework/modulith/test/ScenarioUnitTests.java

+9-7
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ void failsIfNoEventOfExpectedTypeArrives() {
8383
@Test // GH-136
8484
void matchesEventsWithPredicate() throws Throwable {
8585

86-
Consumer<Scenario> consumer = it -> publishObject(it)
86+
Consumer<Scenario> consumer = it -> it.publish(() -> 41)
8787
.forEventOfType(SomeEvent.class)
8888
.matching(__ -> __.payload().startsWith("foo"))
8989
.toArrive();
@@ -194,7 +194,8 @@ void triggersCleanupOnSuccess() {
194194
BiConsumer<String, Integer> verification = mock(BiConsumer.class);
195195
Consumer<Integer> cleanupCallback = mock(Consumer.class);
196196

197-
Consumer<Scenario> consumer = it -> it.stimulate(() -> 41, cleanupCallback)
197+
Consumer<Scenario> consumer = it -> it.stimulate(() -> 41)
198+
.andCleanup(cleanupCallback)
198199
.forEventOfType(String.class)
199200
.toArriveAndVerify(verification);
200201

@@ -211,9 +212,10 @@ void triggersCleanupOnSuccess() {
211212
void triggersCleanupOnFailure() {
212213

213214
BiConsumer<String, Integer> verification = mock(BiConsumer.class);
214-
Consumer<Integer> cleanupCallback = mock(Consumer.class);
215+
Runnable cleanupCallback = mock(Runnable.class);
215216

216-
Consumer<Scenario> consumer = it -> it.stimulate((tx) -> 41, cleanupCallback)
217+
Consumer<Scenario> consumer = it -> it.stimulate((tx) -> 41)
218+
.andCleanup(cleanupCallback)
217219
.andWaitAtMost(WAIT_TIME)
218220
.forEventOfType(String.class)
219221
.toArriveAndVerify(verification);
@@ -223,7 +225,7 @@ void triggersCleanupOnFailure() {
223225
.expectFailure();
224226

225227
verify(verification, never()).accept(any(), any());
226-
verify(cleanupCallback).accept(41);
228+
verify(cleanupCallback).run();
227229
}
228230

229231
@Test // GH-136
@@ -266,8 +268,8 @@ void triggersVerificationOnSuccessfulStateChange() {
266268

267269
Consumer<String> verification = mock(Consumer.class);
268270

269-
Consumer<Scenario> consumer = it -> it.stimulate(() -> 41)
270-
.andWaitForStateChange(delayed("Foo"))
271+
Consumer<Scenario> consumer = it -> it.stimulate(() -> {})
272+
.forStateChange(delayed("Foo"))
271273
.andVerify(verification);
272274

273275
givenAScenario(consumer)

0 commit comments

Comments
 (0)