Axon framework: How to retrieve id of a state-stored aggregate id in a unit test

Viewed 327

I am new to Axon framework and am trying to implement an application using CQRS with state-stored aggregates. The aggregate is created via a command handling constructor that assigns a random UUID as the aggregate identifier.

To test commands on the aggregate, I create the aggregate via a givenCommands(new CreatePlanCommand(...) call. I then want to issue the command under test, UpdatePlanNameCommand in this case, but that command needs the aggregate id that was assigned by the CreatePlanCommand executed in the test setup. Is there a way to retrieve this?

Example test code shown below (see question in comment on when() call):

@Test
public void plan_updatePlan_updatesPlanName() {
    testFixture
            .givenCommands(new CreatePlanCommand(123L, "My Test Plan", funds))
            .when(new UpdatePlanNameCommand(/* How to get aggregate id returned by CreatePlanCommand here? */, "New Name"))
            .expectSuccessfulHandlerExecution()
            .expectState(state -> {
                assertThat(state.getCompanyId(), equalTo(123L));
                assertThat(state.getName(), equalTo("New Name"));
                assertThat(state.getAvailableFunds(), equalTo(funds));
            });
}

The command handler that creates the plan is as follows:

@CommandHandler
public Plan(CreatePlanCommand command, PlanFundValidator planFundValidator) {
    // Use injected domain service to verify that all funds in this plan's lineup actually exist
    planFundValidator.validateFundsExist(command.getAvailableFunds());

    this.id = UUID.randomUUID();
    this.companyId = command.getCompanyId();
    this.name = command.getName();
    this.availableFunds = new HashSet<>(command.getAvailableFunds());
    apply(planCreatedEvent());
}

Update: I was able to get the test working by using .givenState(...) to create the aggregate as follows:

@Test
public void plan_updatePlan_updatesPlanName() {
    AtomicReference<UUID> planId = new AtomicReference<>();
    testFixture
            .givenState(() -> {
                Plan plan = new Plan(new CreatePlanCommand(123L, "My Test Plan", funds), mockPlanFundValidator);
                planId.set(plan.getId());
                return plan;
            })
            .when(new UpdatePlanNameCommand(planId.get(), "New Name"))
            .expectSuccessfulHandlerExecution()
            .expectState(state -> {
                assertThat(state.getCompanyId(), equalTo(123L));
                assertThat(state.getName(), equalTo("New Name"));
                assertThat(state.getAvailableFunds(), equalTo(funds));
            });
}

but this seems overly verbose and a step backwards in terms of test readability and maintenance, so I am still curious if there is a way to accomplish this with .givenCommands(...). Thank you.

1 Answers

All right, let me try to give you some info on that.

In my opinion, the easiest way to achieve it is to move the UUID generation to another Component. In that way, you can clearly mock it on tests.

eg:

@CommandHandler
public Plan(CreatePlanCommand command, PlanFundValidator planFundValidator, UUIDGenerator generator) {
    // Use injected domain service to verify that all funds in this plan's lineup actually exist
    planFundValidator.validateFundsExist(command.getAvailableFunds());

    this.id = generator.generate(); // changed this line
    this.companyId = command.getCompanyId();
    this.name = command.getName();
    this.availableFunds = new HashSet<>(command.getAvailableFunds());
    apply(planCreatedEvent());
}

Doing that, same way you mocked the PlanFundValidator, you also mock the UUIDGenerator making sure you have the needed id.

For the other question:

Related question: is there a way to clear any events that were raised in the given part of a test? For example, if I have a unit test for command "C" that executes commands "A" and "B" as part of its "given" to set up the test, I would like to be able to assert that command "C" (the command under test) raised the expected events and not see any events raised by commands "A" and "B" since those are not the subject of this test and have their own tests that verify they raise the expected events.

The events are not kept from test to test but if those are raised by your own test, than you have to deal with them. By the givenState you find out earlier can also help with this. You just have to setup the state you need and fire the command/event you are testing.

Related