How to insert events directly (for Cucumber test)

I’m trying to figure out how to insert events directly to the event store, whilst also having projections trigger via any event handlers registered.

Looking for guidance on how to wire these up; I’ve tried creating my aggregate (which calls apply for each event already), but I get No UnitOfWork is currently started for this thread..

Basically, for a @Given step in the feature, I want to insert a bunch of events to put the event store (and any projections) into a predetermined state for the test. Creating a command and pushing to the command bus seems logical, but it has some undesirable possible side-effects (e.g. command could be rejected).

Any help really appreciated - TIA
James

Hi James,

assuming you’re on Axon 3, you can use EventBus (or EventStore).publish(…). You can wrap your Event in a GenericDomainEventMessage. Make sure you pass the correct Aggregate Identifier and Sequence number for each of the events, so that Axon can use them to restore your Aggregate instance based on these Events.

Hope this helps.
Cheers,

Allard

Hi Allard,

Thanks for your response. I think I was on the right lines then. I’m using EventStore.appendEvents (we’re on Axon 2.4.6 btw) and following it up with EventBus.publish (the latter seems to be required to trigger the event handlers). Looks like this is all working as I was hoping now.

Thanks for your help! And nice work :slight_smile:

Thanks
James

Hi Allard,

I tried the same thing in Axon 3 by appending GenericDomainEventMessage to eventstore with parameters - aggregate Type, aggregate Identifier, sequence No. and payload. I get this exception -

org.axonframework.commandhandling.model.ConcurrencyException: An event for aggregate [0c1277c1-488b-4c24-8506-424c9e88f0c9] at sequence [91] was already inserted.

Please help !

Hi Harsh,

As the exception suggests, you cannot append an event for a given aggregate identifier with an already existing sequence number.
So, I’ll first shoot at that: did try appending an event for for the aggregate (identifier), for which the sequence number isn’t already in the eventstore ?

Cheers,

Steven

Hi James,

I’ve written test tools several times to do exactly that. First, I’m using the configuration API to get a default axon configuration with an InMemoryEventStorageEngine.

`

final EventStorageEngine eventStorageEngine = new InMemoryEventStorageEngine();

Configuration configuration = DefaultConfigurer
.defaultConfiguration()
.configureEmbeddedEventStore(heinz -> eventStorageEngine)
.configureAggregate(Rolle.class)
.buildConfiguration();
configuration.start();

`

Next you need a test tool class to generate the messages with valid sequence numbers the easy way. Something like this:

`

public class TestTool {
private final EventStorageEngine engine;
private final Map<String, Long> sequenceNumberDirectory = new HashMap<>();
private final List<DomainEventMessage<?>> createdDomainMessages = new ArrayList<>();

public TestTool(EventStorageEngine engine) {
this.engine = engine;
    }

public List<DomainEventMessage> commit() {
engine.appendEvents(createdDomainMessages);
        List<DomainEventMessage> committedMessages = Collections.unmodifiableList(new ArrayList<>(createdDomainMessages));
createdDomainMessages.clear();
sequenceNumberDirectory.clear();
return committedMessages;
    }

    public TestTool rightCreated(String rightId, String description) {
        createdDomainMessages.add(
                createDomainEventMessage(rightId, new RightCreatedEvent(rightId, description))
        );

        return this;
    }

    private <P> DomainEventMessage<P> createDomainEventMessage(String aggregateId, P payload) {
        return new GenericDomainEventMessage<>(
                payload.getClass().getName(),
                aggregateId,
                getNextSequenceNumber(aggregateId),
                payload
        );
    }

private long getNextSequenceNumber(String aggregateId) {
if (sequenceNumberDirectory.get(aggregateId) == null) {
if (engine != null) {
sequenceNumberDirectory.put(aggregateId, Optional.ofNullable(engine.readEvents(aggregateId).getLastSequenceNumber()).orElse(0L));
} else {
sequenceNumberDirectory.put(aggregateId, 0L);
}
}

return sequenceNumberDirectory.put(aggregateId, sequenceNumberDirectory.get(aggregateId) + 1);

}
}

`

Now you can use it with:

List<DomainEventMessage> createdMessages = new TestTool(engine) .rightCreated("1", "The first right") .commit();

The rightCreated method is a sample for a method creating some meaningful event in your domain. The fluent style gives the possibility to chain event creation methods.

Hope this helps