Search code examples
javadomain-driven-designaxon

How can I test an Aggregate that relies on time?


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.


Solution

  • 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()));
    }