Skip to content

Commit

Permalink
feat: throw appropriate exception messages when before and after lamb…
Browse files Browse the repository at this point in the history
…das are not implemented as expected
  • Loading branch information
tmorin committed Feb 18, 2024
1 parent c7420a8 commit ffb052a
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import io.morin.faggregate.api.AggregateManager;
import io.morin.faggregate.api.Output;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
Expand Down Expand Up @@ -75,8 +76,7 @@ public CompletionStage<Void> execute() {
return Optional
// GIVEN STEP - initialize the state of the aggregate with a given state
.ofNullable(scenario.getGiven().getState())
.map(state -> before.initialize(scenario.getGiven().getIdentifier(), state, scenario.getGiven().getEvents())
)
.map(state -> before.store(scenario.getGiven().getIdentifier(), state, scenario.getGiven().getEvents()))
.orElseGet(() -> CompletableFuture.completedStage(null))
// GIVEN STEP - mutate the aggregate with given commands
.thenAccept(unused -> {
Expand All @@ -94,15 +94,24 @@ public CompletionStage<Void> execute() {
// WHEN - create the outcome based on the fetch aggregate state and the output of the command
.thenCompose(output ->
after
.fetch(scenario.getGiven().getIdentifier())
.load(scenario.getGiven().getIdentifier())
.thenApply(currentState -> new Outcome(output, currentState))
)
// THEN - assert the outcome
.thenApply(outcome -> {
// THEN - check actual state with the expected one
Optional
.ofNullable(scenario.getThen().getState())
.ifPresent(expectedState -> stateAsserter.process(scenario, outcome.state, expectedState));
.ifPresent(expectedState ->
stateAsserter.process(
scenario,
Objects.requireNonNull(
outcome.state,
"The after lambda of the suite execution is not implemented!"
),
expectedState
)
);
// THEN - check actual events with the expected ones
Optional
.ofNullable(scenario.getThen().getEvents())
Expand All @@ -123,14 +132,14 @@ public CompletionStage<Void> execute() {
@FunctionalInterface
public interface Before {
/**
* Initialize the state of the aggregate.
* Store the state of the aggregate.
*
* @param identifier the identifier of the aggregate
* @param state the initial state of the aggregate
* @param events a set of initial domain events
* @return a completion stage
*/
CompletionStage<Void> initialize(@NonNull Object identifier, @NonNull Object state, @NonNull List<?> events);
CompletionStage<Void> store(@NonNull Object identifier, @NonNull Object state, @NonNull List<?> events);
}

/**
Expand All @@ -139,12 +148,12 @@ public interface Before {
@FunctionalInterface
public interface After {
/**
* Fetch the state of the aggregate.
* Load the state of the aggregate.
*
* @param identifier the identifier of the aggregate
* @return the embedded in a CompletionStage
* @return the state of the aggregate as a completion stage
*/
CompletionStage<Object> fetch(@NonNull Object identifier);
CompletionStage<Object> load(@NonNull Object identifier);
}

/**
Expand All @@ -159,7 +168,7 @@ static class Outcome {
final Output<?> output;

/**
* The state fetched using {@link After#fetch(Object)}.
* The state fetched using {@link After#load(Object)}.
*/
final Object state;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ public class Suite {
/**
* Execute the scenarios sequentially.
*
* @param am the Aggregate Manager
* @return a completion stage
* @param am the Aggregate Manager
* @param <I> The type of the identifier
* @return a completion stage
*/
@SneakyThrows
public <I> CompletableFuture<Void> execute(@NonNull AggregateManager<I> am) {
Expand All @@ -34,12 +34,20 @@ public <I> CompletableFuture<Void> execute(@NonNull AggregateManager<I> am) {

/**
* Execute the scenarios sequentially.
* <p>
* The before lambda is executed before the _Given_ phase.
* Its purpose is to store the state of the aggregate.
* As long as the state of the artifact is not provided during the _Given_ phase, the before lambda is not mandatory.
* <p>
* The after lambda is executed after the _Then_ phase.
* Its purpose is to load the state of the aggregate.
* As long as the state of the artifact is not validated during the _Then_ phase, the after lambda is not mandatory.
*
* @param am the Aggregate Manager
* @param before the before lambda
* @param after the after lambda
* @param before the optional before lambda
* @param after the optional after lambda
* @param <I> The type of the identifier
* @return a completion stage
* @param <I> The type of the identifier
*/
@SneakyThrows
@SuppressWarnings("unchecked")
Expand All @@ -58,7 +66,11 @@ public <I> CompletableFuture<Void> execute(
.before(
Optional
.ofNullable(before)
.orElse((identifier, state, events) -> CompletableFuture.completedStage(null))
.orElse((identifier, state, events) ->
CompletableFuture.failedFuture(
new UnsupportedOperationException("No state storing is implemented.")
)
)
)
.after(Optional.ofNullable(after).orElse(identifier -> CompletableFuture.completedStage(null)))
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,9 @@ void shouldSuccess() {
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).add(expectedEvent).build()));
when(am.execute(eq("id"), any(String.class)))
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).build()));
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(expectedState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(expectedState));
Assertions.assertDoesNotThrow(() -> executor.execute().toCompletableFuture().get());
verify(before, new Only()).initialize(eq("id"), eq(initialState), anyList());
verify(before, new Only()).store(eq("id"), eq(initialState), anyList());
verify(am, new Times(2)).execute(eq("id"), anyString());
verify(asserter, new Only()).accept(eq(expectedState), anyList());
}
Expand All @@ -94,17 +94,17 @@ void shouldSuccess() {
void shouldSuccessWhenInitialState() {
when(am.execute(eq("id"), any(ApplyUppercase.class)))
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).add(expectedEvent).build()));
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(expectedState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(expectedState));
Assertions.assertDoesNotThrow(() -> executorAlt.execute().toCompletableFuture().get());
verify(before, new NoInteractions()).initialize(any(), any(), any());
verify(before, new NoInteractions()).store(any(), any(), any());
verify(am, new Only()).execute(eq("id"), any(ApplyUppercase.class));
}

@Test
void shouldFailedWhenWrongExpectedState() {
when(am.execute(any(), any()))
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).add(expectedEvent).build()));
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(initialState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(initialState));

Assertions.assertThrows(ExecutionException.class, () -> executor.execute().toCompletableFuture().get());
}
Expand All @@ -113,7 +113,7 @@ void shouldFailedWhenWrongExpectedState() {
void shouldFailedWhenWrongExpectedEvent() {
when(am.execute(any(), any()))
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).add(wrongExpectedEvent).build()));
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(expectedState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(expectedState));

Assertions.assertThrows(ExecutionException.class, () -> executor.execute().toCompletableFuture().get());
}
Expand All @@ -126,7 +126,7 @@ void shouldFailedWhenTooMuchExpectedEvents() {
OutputBuilder.get(null).add(expectedEvent).add(wrongExpectedEvent).build()
)
);
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(expectedState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(expectedState));

Assertions.assertThrows(ExecutionException.class, () -> executor.execute().toCompletableFuture().get());
}
Expand All @@ -143,7 +143,7 @@ void shouldFailedWhenAsserterFails() {
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).add(expectedEvent).build()));
when(am.execute(eq("id"), any(String.class)))
.thenReturn(CompletableFuture.completedFuture(OutputBuilder.get(null).build()));
when(after.fetch(any())).thenReturn(CompletableFuture.completedStage(expectedState));
when(after.load(any())).thenReturn(CompletableFuture.completedStage(expectedState));
doThrow(new IllegalStateException()).when(asserter).accept(any(), any());

Assertions.assertThrows(ExecutionException.class, () -> executor.execute().toCompletableFuture().get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ void shouldExecute() {
when(scenarioC.getThen()).thenReturn(then);

when(am.execute(any(), any())).thenReturn(CompletableFuture.completedFuture(output));
when(after.fetch(any())).thenReturn(CompletableFuture.completedFuture(null));
when(after.load(any())).thenReturn(CompletableFuture.completedFuture(null));

assertDoesNotThrow(() ->
Suite
Expand All @@ -77,7 +77,7 @@ void shouldExecute() {
);

verify(am, Mockito.times(3)).execute(any(), any());
verify(after, Mockito.times(3)).fetch(any());
verify(after, Mockito.times(3)).load(any());

verify(scenarioA, Mockito.times(4)).getGiven();
verify(scenarioA, Mockito.times(1)).getWhen();
Expand Down

0 comments on commit ffb052a

Please sign in to comment.