Configuring Axon in Spring for integration testing

Viewed 409

Still new to java and axon, having troubles to setup integration test suite using spring and axon.

Basically, I have some process, which:

  • starts with command1
  • goes to command handler on aggregate and event1 is dispatched
  • event1 is processed by some policy (event listener) and command2 is generated
  • command2 is processed by command handler service (thus has no aggregate id) because I need to process something with third party
  • event2 is dispatched and processed by another event listener, so command3 is dispatched
  • command3 is processed by aggregate and event3 is dispatched

I use axon tests for unit testing aggregates and it works great.

But I would like to add "integration" tests to:

  1. check the whole flow, like: when command1 dispatched then event1, event2, event3 is recorded (actually don't care about fields, just to have confidence the whole thing is glued as expected)
  2. check the service command handler work as expected in manner "when command2 dispatched, then event2 is recorded"

I was partly succesful with 1) by doing:

package com.example.integration;

import org.axonframework.commandhandling.gateway.CommandGateway;
import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.annotation.Import;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.UUID;

@RunWith(SpringRunner.class)
@SpringBootTest
@Import(IntegrationTestConfig.class)
public class ExampleIntegrationTest
{

    @Autowired
    private CommandGateway commandGateway;

    @Autowired
    private EmbeddedEventStore eventStore;

    @Test
    public void itShouldStoreCorrectEvents() {
        var aggregateId = UUID.randomUUID().toString();
        var exampleCommand = new ExampleCommand(aggregateId);

        whenCommandIsDispatched(exampleCommand);

        thenExpectedEventsRecorded(
                Arrays.asList(
                        Event1.class,
                        Event3.class
                ),
                aggregateId
        );
    }

    private void whenCommandIsDispatched(Command command)
    {
        commandGateway.sendAndWait(command);
    }

    private void thenExpectedEventsDispatched(java.util.List<Class<?>> expectedEventTypes, String id)
    {
        var events = eventStore.readEvents(id);
        while (events.hasNext()) {
            Assert.assertEquals(
                    expectedEventTypes.get(Math.toIntExact(events.getLastSequenceNumber())),
                    events.next().getPayloadType()
            );
        }
    }
}

And having IntegrationConfigTest:

package com.example.library.test;

import org.axonframework.eventhandling.EventBus;
import org.axonframework.eventsourcing.eventstore.EmbeddedEventStore;
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
import org.springframework.boot.test.context.TestConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;

@TestConfiguration
public class IntegrationTestConfig {

    @Bean
    @Primary
    public static EventBus createSpyEventBus() {
        return new EmbeddedEventStore.Builder().storageEngine(new InMemoryEventStorageEngine()).build();
    }
}

Which is nice and test passes, but I didn't find a way how to test events dispatched from the service command handler (Event2 in my example) - id of aggregate is needed here: eventStore.readEvents(id). EDIT: But I dont want to put aggregate id in, since event was not recorded on any aggregate - it was dispatched right into event gateway in service/singleton command handler.

Any ideas how can I get all dispatched events so I can also assert Event2 was dispatched?

Or am I missing something here? :)

2 Answers

What you can do if you want to validate the events in the EventStore, is use the EventStore#openStream(TrackingToken) operation.

This will open a stream of the events contained in the store, from a definable (read: the TrackingToken) point in time. By adjusting the token you provide, you can pick the start position. To construct a token, you can use the EventStore again, that provides the following operations:

  • createTailToken() - starts the stream at the beginning
  • createHeadToken() - starts the stream at the end
  • createTokenAt(Instant dateTime) - starts the stream at Instant
  • createTokenSince(Duration duration) - starts the stream since Duration

Given the smaller scope of your test, I think the createTailToken would be fine.

The initializing command handler that receives command1 to create the aggregate might return the aggregateId, so you can fetch the returned result and use the id for future reference.

 ...
 public class MyAggregate {
     @CommandHandler
     public static MyAggregate create(CreateCmd cmd) {
          MyAggregate a = new MyAggregate();
          AggregateLifecycle.apply(new CreatedEvent(UUID.randomUuid().toString());
          return a;
     }
 }
 ....

 String generatedId = commandGateway.sendAndWait(new CreateCmd(null));
 .... // continue
Related