8. Testing

One of the biggest benefits of CQRS, and especially that of event sourcing is that it is possible to express tests purely in terms of Events and Commands. Both being functional components, Events and Commands have clear meaning to the domain expert or business owner. This means that tests expressed in terms of Events and Commands don't only have a functional meaning, it also means that they hardly depend on any implementation choices.

The features described in this chapter require the axon-test module, which can be obtained by configuration a maven dependency (use <artifactId>axon-text</artifactId>) or from the full package download.

8.1. Behavior Driven Testing

The command handling component is typically the component in any CQRS based architecture that contains the most complexity. Being more complex than the others, this also means that there are extra test related requirements for this component. Simply put: the more complex a component, the better it must be tested.

Although being more complex, the API of a command handling component is fairly easy. It has command coming in, and events going out. In some cases, there might be a query as part of command execution. Other than that, commands and events are the only part of the API. This means that it is possible to completely define a test scenario in terms of events and commands. Typically, in the shape of:

  • given certain events in the past,

  • when executing this command,

  • expect these events to be published and/or stored.

Axon Framework provides a test fixture that allows you to do exactly that. This GivenWhenThenTestFixture allows you to configure a certain infrastructure, composed of the necessary command handler and repository, and express you scenario in terms of given-when-then events and commands.

The following example shows the usage of the given-when-then test fixture with JUnit 4:

public class MyCommandComponentTest {

    private FixtureConfiguration fixture;

    @Before
    public void setUp() {
        fixture = Fixtures.newGivenWhenThenFixture(); (1)
        MyCommandHandler myCommandHandler = new MyCommandHandler(
                              fixture.createGenericRepository(MyAggregate.class)); (2)
        fixture.registerAnnotatedCommandHandler(myCommandHandler); (3)
    }

    @Test
    public void testFirstFixture() {
        fixture.given(new MyEvent(1)) (4)
               .when(new TestCommand())
               .expectVoidReturnType()
               .expectEvents(new MyEvent(2));
    }
}
1

This line creates a fixture instance that can deal with given-when-then style tests. It is created in configuration stage, which allows us to configure the components that we need to process the command, such as command handler and repository. An event bus and command bus are automatically created as part of the fixture.

2

The createGenericRepository method creates, as expected, a GenericEventSourcingRepository instance capable of storing MyAggregate instances. This requires some conventions on the MyAggregate class, as described in Section 5.2, “Event Sourcing repositories”.

3

The registerAnnotatedCommandHandler method will register any bean as being an @CommandHandler with the command bus. All supported command types are automatically registered with the event bus.

4

These four lines define the actual scenario and its expected result. The first line defines the events that happened in the past. These events define the state of the aggregate under test. In practical terms, these are the events that the event store returns when an aggregate is loaded. The second line defines the command that we wish to execute against our system. Finally, we have two more methods that define expected behavior. In the example, we use the recommended void return type. The last method defines that we expect a single event as result of the command execution.

The given-when-then test fixture defines three stages: configuration, execution and validation. Each of these stages is represented by a different interface: FixtureConfiguration, TestExecutor and ResultValidator, respectively. The static newGivenWhenThenFixture() method on the Fixtures class provides a reference to the first of these, which in turn may provide the validator, and so forth.

[Note]Note

To make optimal use of the migration between these stages, it is best to use the fluent interface provided by these methods, as shown in the example above.

Configuration

During the configuration phase, you provide the building blocks required to execute the test. Specialized versions of the event bus, command bus and event store are provided as part of the fixture. There are getters in place to obtain references to them. The repository and command handlers need to be provided. This can be done using the registerRepository and registerCommandHandler (or registerAnnotatedCommandHandler) methods. If your aggregate allows the use of a generic repository, you can use the createGenericRepository method to create a generic repository and register it with the fixture in a single call. The example above uses this feature.

If the command handler and repository are configured, you can define the "given" events. These events need to be subclasses of DomainEvent, as they represent events coming from the event store. You do not need to set aggregate identifiers of sequence numbers. The fixture will inject those for you (using the aggregate identifier exposed by getAggregateIdentifier and a sequence number starting with 0.

Execution

The execution phase allows you to provide a command to be executed against the command handling component. That's all. Note that successful execution of this command requires that a command handler that can handle this type of command has been configured with the test fixture.

Validation

The last phase is the validation phase, and allows you to check on the activities of the command handling component. This is done purely in terms of return values and events (both stored and dispatched).

The test fixture allows you to validate return values of your command handlers. You can explicitly define an expected void return value or any arbitrary value. You may also express the expectancy of an exception.

The other component is validation of stored and dispatched events. In most cases, the stored and dispatched are equal. In some cases however, you may dispatch events (e.g. ApplicationEvent) that are not stored in the event store. In the first case, you can use the expectEvents method to validate events. In the latter case, you may use the expectPublishedEvents and expectStoredEvents methods to validate published and stored events, respectively.

There are two ways of matching expected events.

The first is to pass in Event instances that need to be literally compared with the actual events. All properties of the expected Events are compared (using equals()) with their counterparts in the actual Events. If one of the properties is not equal, the test fails and an extensive error report is generated.

The other way of expressing expectancies is using Matchers (provided by the Hamcrest library). Matcher is an interface prescribing two methods: matches(Object) and describeTo(Description). The first returns a boolean to indicate whether the matcher matches or not. The second allows you to express your expectation. For example, a "GreaterThanTwoMatcher" could append "any event with value greater than two" to the description. Descriptions allow expressive error messages to be created about why a test case fails.