Skip to content

Commit e364f52

Browse files
committed
Given-When-Then DSL for unit testing domain components
1 parent eca3cc3 commit e364f52

6 files changed

Lines changed: 240 additions & 34 deletions

File tree

README.md

Lines changed: 53 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,9 @@ Notice that `Decider` implements an
7070
interface [`IDecider`](src/main/java/com/fraktalio/fmodel/domain/decider/IDecider.java) to communicate the contract.
7171

7272
<details>
73-
<summary>Example / Test</summary>
73+
<summary>Example / Given-When-Then Test</summary>
74+
75+
A fluent [test DSL/builder](src/test/java/com/fraktalio/fmodel/dsl/DeciderDSL.java) to support `Given-When-Then` format
7476

7577
```java
7678
class DeciderTest {
@@ -131,17 +133,40 @@ class DeciderTest {
131133
(p) -> new NumberState(p.first(), p.second())
132134
);
133135

134-
assertIterableEquals(List.of(oddNumberAddedEvent), oddDecider.decide().apply(addOddNumberCommand, oddState));
135-
assertIterableEquals(List.of(evenNumberAddedEvent), evenDecider.decide().apply(addEvenNumberCommand, evenState));
136-
assertIterableEquals(List.of(oddNumberAddedEvent), decider.decide().apply(addOddNumberCommand, state));
137136

138-
assertEquals(new OddNumberState(1), oddDecider.evolve().apply(oddState, oddNumberAddedEvent));
139-
assertEquals(new EvenNumberState(2), evenDecider.evolve().apply(evenState, evenNumberAddedEvent));
140-
assertEquals(new NumberState(new EvenNumberState(0), new OddNumberState(1)), decider.evolve().apply(state, oddNumberAddedEvent));
141-
assertEquals(new NumberState(new EvenNumberState(2), new OddNumberState(0)), decider.evolve().apply(state, evenNumberAddedEvent));
137+
givenState(oddDecider, oddState)
138+
.whenCommand(addOddNumberCommand)
139+
.thenState(new OddNumberState(1));
140+
141+
givenEvents(oddDecider, List.of())
142+
.whenCommand(addOddNumberCommand)
143+
.thenEvents(List.of(oddNumberAddedEvent));
144+
145+
// Even decider: given evenState + addEvenNumberCommand -> then evenNumberAddedEvent
146+
givenState(evenDecider, evenState)
147+
.whenCommand(addEvenNumberCommand)
148+
.thenState(new EvenNumberState(2));
149+
150+
givenEvents(evenDecider, List.of())
151+
.whenCommand(addEvenNumberCommand)
152+
.thenEvents(List.of(evenNumberAddedEvent));
153+
154+
// Combined decider: given state + odd command -> events
155+
givenEvents(decider, List.of())
156+
.whenCommand(addOddNumberCommand)
157+
.thenEvents(List.of(oddNumberAddedEvent));
158+
159+
// Combined decider: given state + odd command -> new state
160+
givenState(decider, state)
161+
.whenCommand(addOddNumberCommand)
162+
.thenState(new NumberState(new EvenNumberState(0), new OddNumberState(1)));
163+
164+
// Combined decider: given state + even command -> new state
165+
givenState(decider, state)
166+
.whenCommand(addEvenNumberCommand)
167+
.thenState(new NumberState(new EvenNumberState(2), new OddNumberState(0)));
142168
}
143169
}
144-
145170
```
146171

147172
</details>
@@ -175,16 +200,20 @@ Notice that `View` implements an interface [`IView`](src/main/java/com/fraktalio
175200
communicate the contract.
176201

177202
<details>
178-
<summary>Example / Test</summary>
203+
<summary>Example / Given-When-Then Test</summary>
204+
205+
A fluent [test DSL/builder](src/test/java/com/fraktalio/fmodel/dsl/ViewDSL.java) to support `Given-When-Then` format
179206

180207
```java
181208
class ViewTest {
182209
@Test
183210
void viewTest() {
184211
var oddNumberAddedEvent = new OddNumberAddedEvent(1);
185212
var evenNumberAddedEvent = new EvenNumberAddedEvent(2);
213+
186214
var oddState = new OddNumberState(0);
187215
var evenState = new EvenNumberState(0);
216+
188217
var state = new NumberState(evenState, oddState);
189218

190219
View<OddNumberState, ? super OddEvent> oddView = new View<>(
@@ -206,25 +235,32 @@ class ViewTest {
206235
);
207236

208237
// Combining two views into one
209-
View<Pair<EvenNumberState, OddNumberState>, ? super Event> _decider = View.combine(
238+
View<Pair<EvenNumberState, OddNumberState>, ? super Event> _view = View.combine(
210239
evenView, EvenEvent.class,
211240
oddView, OddEvent.class
212241
);
213242
// Combining two views into one, plus mapping inconvenient `Pair` into more domain specific `NumberState`
214-
View<NumberState, ? super Event> decider = View
243+
View<NumberState, ? super Event> view = View
215244
.combine(evenView, EvenEvent.class, oddView, OddEvent.class)
216245
.dimapState(
217246
(ns) -> new Pair<>(ns.evenNumber(), ns.oddNumber()),
218247
(p) -> new NumberState(p.first(), p.second())
219248
);
220249

221-
assertEquals(new OddNumberState(1), oddView.evolveView().apply(oddState, oddNumberAddedEvent));
222-
assertEquals(new EvenNumberState(2), evenView.evolveView().apply(evenState, evenNumberAddedEvent));
223-
assertEquals(new NumberState(new EvenNumberState(0), new OddNumberState(1)), decider.evolveView().apply(state, oddNumberAddedEvent));
224-
assertEquals(new NumberState(new EvenNumberState(2), new OddNumberState(0)), decider.evolveView().apply(state, evenNumberAddedEvent));
250+
// --- DSL usage ---
251+
givenEvents(oddView, List.of(oddNumberAddedEvent))
252+
.thenState(new OddNumberState(1));
253+
254+
givenEvents(evenView, List.of(evenNumberAddedEvent))
255+
.thenState(new EvenNumberState(2));
256+
257+
givenEvents(view, List.of(oddNumberAddedEvent))
258+
.thenState(new NumberState(new EvenNumberState(0), new OddNumberState(1)));
259+
260+
givenEvents(view, List.of(evenNumberAddedEvent))
261+
.thenState(new NumberState(new EvenNumberState(2), new OddNumberState(0)));
225262
}
226263
}
227-
228264
```
229265

230266
</details>

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<plugin>
2828
<groupId>org.apache.maven.plugins</groupId>
2929
<artifactId>maven-compiler-plugin</artifactId>
30+
<version>3.14.0</version>
3031
<configuration>
3132
<source>23</source>
3233
<target>23</target>

src/test/java/com/fraktalio/fmodel/domain/example/DeciderTest.java

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77

88
import java.util.List;
99

10-
import static org.junit.jupiter.api.Assertions.assertEquals;
11-
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
10+
import static com.fraktalio.fmodel.dsl.DeciderDSL.givenEvents;
11+
import static com.fraktalio.fmodel.dsl.DeciderDSL.givenState;
1212

1313
class DeciderTest {
1414

@@ -69,13 +69,37 @@ void deciderTest() {
6969
(p) -> new NumberState(p.first(), p.second())
7070
);
7171

72-
assertIterableEquals(List.of(oddNumberAddedEvent), oddDecider.decide().apply(addOddNumberCommand, oddState));
73-
assertIterableEquals(List.of(evenNumberAddedEvent), evenDecider.decide().apply(addEvenNumberCommand, evenState));
74-
assertIterableEquals(List.of(oddNumberAddedEvent), decider.decide().apply(addOddNumberCommand, state));
7572

76-
assertEquals(new OddNumberState(1), oddDecider.evolve().apply(oddState, oddNumberAddedEvent));
77-
assertEquals(new EvenNumberState(2), evenDecider.evolve().apply(evenState, evenNumberAddedEvent));
78-
assertEquals(new NumberState(new EvenNumberState(0), new OddNumberState(1)), decider.evolve().apply(state, oddNumberAddedEvent));
79-
assertEquals(new NumberState(new EvenNumberState(2), new OddNumberState(0)), decider.evolve().apply(state, evenNumberAddedEvent));
73+
givenState(oddDecider, oddState)
74+
.whenCommand(addOddNumberCommand)
75+
.thenState(new OddNumberState(1));
76+
77+
givenEvents(oddDecider, List.of())
78+
.whenCommand(addOddNumberCommand)
79+
.thenEvents(List.of(oddNumberAddedEvent));
80+
81+
// Even decider: given evenState + addEvenNumberCommand -> then evenNumberAddedEvent
82+
givenState(evenDecider, evenState)
83+
.whenCommand(addEvenNumberCommand)
84+
.thenState(new EvenNumberState(2));
85+
86+
givenEvents(evenDecider, List.of())
87+
.whenCommand(addEvenNumberCommand)
88+
.thenEvents(List.of(evenNumberAddedEvent));
89+
90+
// Combined decider: given state + odd command -> events
91+
givenEvents(decider, List.of())
92+
.whenCommand(addOddNumberCommand)
93+
.thenEvents(List.of(oddNumberAddedEvent));
94+
95+
// Combined decider: given state + odd command -> new state
96+
givenState(decider, state)
97+
.whenCommand(addOddNumberCommand)
98+
.thenState(new NumberState(new EvenNumberState(0), new OddNumberState(1)));
99+
100+
// Combined decider: given state + even command -> new state
101+
givenState(decider, state)
102+
.whenCommand(addEvenNumberCommand)
103+
.thenState(new NumberState(new EvenNumberState(2), new OddNumberState(0)));
80104
}
81105
}

src/test/java/com/fraktalio/fmodel/domain/example/ViewTest.java

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import com.fraktalio.fmodel.domain.view.View;
66
import org.junit.jupiter.api.Test;
77

8-
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import java.util.List;
99

10-
class ViewTest {
10+
import static com.fraktalio.fmodel.dsl.ViewDSL.givenEvents;
1111

12+
class ViewTest {
1213
@Test
1314
void viewTest() {
1415
var oddNumberAddedEvent = new OddNumberAddedEvent(1);
@@ -38,21 +39,30 @@ void viewTest() {
3839
);
3940

4041
// Combining two views into one
41-
View<Pair<EvenNumberState, OddNumberState>, ? super Event> _decider = View.combine(
42+
View<Pair<EvenNumberState, OddNumberState>, ? super Event> _view = View.combine(
4243
evenView, EvenEvent.class,
4344
oddView, OddEvent.class
4445
);
4546
// Combining two views into one, plus mapping inconvenient `Pair` into more domain specific `NumberState`
46-
View<NumberState, ? super Event> decider = View
47+
View<NumberState, ? super Event> view = View
4748
.combine(evenView, EvenEvent.class, oddView, OddEvent.class)
4849
.dimapState(
4950
(ns) -> new Pair<>(ns.evenNumber(), ns.oddNumber()),
5051
(p) -> new NumberState(p.first(), p.second())
5152
);
5253

53-
assertEquals(new OddNumberState(1), oddView.evolveView().apply(oddState, oddNumberAddedEvent));
54-
assertEquals(new EvenNumberState(2), evenView.evolveView().apply(evenState, evenNumberAddedEvent));
55-
assertEquals(new NumberState(new EvenNumberState(0), new OddNumberState(1)), decider.evolveView().apply(state, oddNumberAddedEvent));
56-
assertEquals(new NumberState(new EvenNumberState(2), new OddNumberState(0)), decider.evolveView().apply(state, evenNumberAddedEvent));
54+
// --- DSL usage ---
55+
givenEvents(oddView, List.of(oddNumberAddedEvent))
56+
.thenState(new OddNumberState(1));
57+
58+
givenEvents(evenView, List.of(evenNumberAddedEvent))
59+
.thenState(new EvenNumberState(2));
60+
61+
givenEvents(view, List.of(oddNumberAddedEvent))
62+
.thenState(new NumberState(new EvenNumberState(0), new OddNumberState(1)));
63+
64+
givenEvents(view, List.of(evenNumberAddedEvent))
65+
.thenState(new NumberState(new EvenNumberState(2), new OddNumberState(0)));
5766
}
5867
}
68+
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.fraktalio.fmodel.dsl;
2+
3+
4+
import com.fraktalio.fmodel.domain.decider.IDecider;
5+
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Objects;
9+
10+
import static org.junit.jupiter.api.Assertions.assertEquals;
11+
import static org.junit.jupiter.api.Assertions.assertIterableEquals;
12+
13+
14+
public final class DeciderDSL {
15+
16+
private DeciderDSL() {
17+
}
18+
19+
// --------------------------
20+
// givenEvents
21+
// --------------------------
22+
public static <C, S, E> GivenEventsBuilder<C, S, E> givenEvents(
23+
IDecider<C, S, E> decider,
24+
List<E> events
25+
) {
26+
return new GivenEventsBuilder<>(decider, events);
27+
}
28+
29+
// --------------------------
30+
// givenState
31+
// --------------------------
32+
public static <C, S, E> GivenStateBuilder<C, S, E> givenState(
33+
IDecider<C, S, E> decider,
34+
S state
35+
) {
36+
return new GivenStateBuilder<>(decider, state);
37+
}
38+
39+
// --------------------------
40+
// Builders
41+
// --------------------------
42+
43+
public static class GivenEventsBuilder<C, S, E> {
44+
private final IDecider<C, S, E> decider;
45+
private final List<E> priorEvents;
46+
47+
GivenEventsBuilder(IDecider<C, S, E> decider, List<E> priorEvents) {
48+
this.decider = Objects.requireNonNull(decider);
49+
this.priorEvents = priorEvents;
50+
}
51+
52+
public WhenEventsBuilder<C, S, E> whenCommand(C command) {
53+
var currentState = priorEvents.stream().reduce(decider.initialState().get(), (s, e) -> decider.evolve().apply(s, e), (s, s2) -> s);
54+
55+
List<E> decided = decider.decide().apply(command, currentState);
56+
return new WhenEventsBuilder<>(decided);
57+
}
58+
}
59+
60+
public static class WhenEventsBuilder<C, S, E> {
61+
private final List<E> actualEvents;
62+
63+
WhenEventsBuilder(List<E> actualEvents) {
64+
this.actualEvents = new ArrayList<>(actualEvents);
65+
}
66+
67+
public void thenEvents(List<E> expected) {
68+
assertIterableEquals(expected, actualEvents);
69+
}
70+
}
71+
72+
public static class GivenStateBuilder<C, S, E> {
73+
private final IDecider<C, S, E> decider;
74+
private final S priorState;
75+
76+
GivenStateBuilder(IDecider<C, S, E> decider, S priorState) {
77+
this.decider = Objects.requireNonNull(decider);
78+
this.priorState = priorState;
79+
}
80+
81+
public WhenStateBuilder<C, S, E> whenCommand(C command) {
82+
S currentState = (priorState != null) ? priorState : decider.initialState().get();
83+
List<E> decidedEvents = decider.decide().apply(command, currentState);
84+
85+
S newState = decidedEvents.stream().reduce(currentState, (s, e) -> decider.evolve().apply(s, e), (s, s2) -> s);
86+
87+
return new WhenStateBuilder<>(newState);
88+
}
89+
}
90+
91+
public static class WhenStateBuilder<C, S, E> {
92+
private final S actualState;
93+
94+
WhenStateBuilder(S actualState) {
95+
this.actualState = actualState;
96+
}
97+
98+
public void thenState(S expected) {
99+
assertEquals(expected, actualState);
100+
}
101+
}
102+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.fraktalio.fmodel.dsl;
2+
3+
4+
import com.fraktalio.fmodel.domain.view.IView;
5+
6+
import java.util.List;
7+
8+
import static org.junit.jupiter.api.Assertions.assertEquals;
9+
10+
public class ViewDSL {
11+
12+
public static <S, E> GivenEventsBuilder<S, E> givenEvents(IView<S, E> view, List<E> events) {
13+
return new GivenEventsBuilder<>(view, events);
14+
}
15+
16+
public static class GivenEventsBuilder<S, E> {
17+
private final IView<S, E> view;
18+
private final List<E> events;
19+
20+
GivenEventsBuilder(IView<S, E> view, List<E> events) {
21+
this.view = view;
22+
this.events = events;
23+
}
24+
25+
public void thenState(S expected) {
26+
S result = events.stream()
27+
.reduce(view.initialViewState().get(),
28+
view.evolveView(),
29+
(s1, s2) -> s2);
30+
assertEquals(expected, result);
31+
}
32+
}
33+
}

0 commit comments

Comments
 (0)