I am trying to model a time keeping application.
Ordinarily when I have a class that depends on time, I can provide an overloaded constructor or method to be able to inject a Clock into that method or class and be able to test its behavior.
If I have a command that needs to be able to pass the current time into an event, how can this work in the aggregate of an axon based application?
@Aggregate
@Slf4j
public class TimeCard {
@AggregateIdentifier
private String employeeName;
private Instant clockInTime;
public TimeCard() {
//Axon requires empty constructor on aggregate
}
@CommandHandler
public TimeCard(ClockInCommand cmd) {
AggregateLifecycle.apply(new ClockInEvent(cmd.getEmployeeName(), Instant.now()));
}
@EventSourcingHandler
public void on(ClockInEvent event) {
this.employeeName = event.getEmployeeName();
this.clockInTime = event.getClockInTime();
}
}
It seemed that the test fixture handled that cleanly for me by providing methods to provide the time. Here is my test method:
@Test
void testClockInCommand() {
testFixture.givenNoPriorActivity()
.andGivenCurrentTime(clock.instant())
.when(new ClockInCommand("GoldFlsh"))
.expectEvents(new ClockInEvent("GoldFlsh", testFixture.currentTime()));
}
But my event did end up being different by a fraction of a second.
Expected <2020-02-02T13:47:20.684344700Z> but got <2020-02-02T13:47:20.954347700Z>
What's the best way to handle this? Should commands only take in time from upstream? Or can I inject a clock somehow for testing.
When relying on time in Aggregates (and other axon types) you can use the GenericEventMessage.clock
which defaults to System.UTC in most runtime configurations.
The Testfixture will override this to be a fixed time during tests. Update the use of Instant.now() to use this clock.
@CommandHandler
public TimeCard(ClockInCommand cmd) {
AggregateLifecycle.apply(new ClockInEvent(cmd.getEmployeeName(), GenericEventMessage.clock.instant()));
}