Skip to content

Command Handling Tests

OpenCQRS encapsulates the details of command handling by means of the CommandRouter. Hence, developers can focus on defining inputs in terms of commands, outputs in terms of newly published events, and the corresponding command handling logic.

While commands and events are typically designed as simple DTO classes, requiring no explicit tests. The command handling logic can (and should) be unit tested using the CommandHandlingTestFixture instead of using the CommandRouter for integration testing.

Command Handling Test Fixture

CommandHandlingTestFixture provides a given-when-then fluent API to write command handling tests. It mimics the behavior of the CommandRouter and therefore needs to be initialized with the CommandHandlerDefinition and its associated StateRebuildingHandlerDefinitions, prior to test execution.

Differences between CommandRouter and CommandHandlingTestFixture

CommandHandlingTestFixture mimics CommandRouter with the following exceptions:

  • No event store or EsdbClient is involved during test execution. All given and captured events are processed in-memory.
  • No event upcasting and type resolution is involved, that is the given events are passed as-is to the StateRebuildingHandlerDefinitions.
  • Raw Event information is stubbed, as no event store is involved, see the JavaDoc of CommandHandlerDefinition.
  • Automatic command meta-data propagation to newly published events is ignored.
  • Any state reconstructed prior to command execution won't be cached.

The following JUnit test exemplarily shows how to express tests using the CommandHandlingTestFixture for the PurchaseBookCommand handler:

@Test
public void bookCanBePurchased() {
    CommandHandlingTestFixture<PurchaseBookCommand> fixture = /* (1)! */

    fixture.givenNothing() /* (2)! */
            .when(  /* (3)! */
                    new PurchaseBookCommand(
                            "4711", 
                            "JRR Tolkien", 
                            "LOTR", 
                            435
                    )
            )
            .expectSuccessfulExecution() /* (4)! */
            .expectSingleEvent(  /* (5)! */
                    new BookPurchasedEvent(
                            "4711", 
                            "JRR Tolkien", 
                            "LOTR", 
                            435
                    )
            );
}
  1. initialization omitted for brevity
  2. initializes the fixture with an empty state, i.e. no prior events published
  3. executes PurchaseBookCommand command handler, capturing any newly published events
  4. verifies successful command handler execution
  5. verifies that exactly one BookPurchasedEvent was published that equals the given reference event

Fluent API

CommandHandlingTestFixture requires a generic Command subtype identifying the command under test. Based on that, it provides a fluent API which can easily be inspected using IDE auto-completion in combination with the associated JavaDoc method descriptions.

Manual Initialization

CommandHandlingTestFixture can be manually instantiated using the CommandHandlerDefinition under test and its associated StateRebuildingHandlerDefinitions, as shown in the following example:

@Test
public void bookCanBePurchased() {
    CommandHandlingTestFixture<PurchaseBookCommand> fixture =
            CommandHandlingTestFixture
                    .withStateRebuildingHandlerDefinitions(/* (1)! */)
                    .using(/* (2)! */);

    // ...
}
  1. Variable number of StateRebuildingHandlerDefinition instances. Could be omitted for this test, since PurchaseBookCommand is a creational command.
  2. The CommandHandlerDefinition to be tested.

Spring Boot Test Slice

When using Spring command handler definition and state rebuilding handler definition beans, test classes may be annotated using CommandHandlingTest, providing autoconfigured CommandHandlingTestFixture for every CommandHandlerDefinition within the Spring context. This relieves developer from manually initializing the fixture in favor of auto-wiring it, as shown in the following example:

@CommandHandlingTest
public class BookHandlingTest {

    @Test
    public void bookCanBePurchased(@Autowired CommandHandlingTestFixture<PurchaseBookCommand>/* (1)! */ fixture) {
        fixture.givenNothing()
                .when(
                        new PurchaseBookCommand(
                                "4711",
                                "JRR Tolkien",
                                "LOTR",
                                435
                        )
                )
                .expectSuccessfulExecution()
                .expectSingleEvent(
                        new BookPurchasedEvent(
                                "4711",
                                "JRR Tolkien",
                                "LOTR",
                                435
                        )
                );
    }
}
  1. It is important to use the unambiguous generic command type, which uniquely identifies the autoconfigured fixture. Raw usage of CommandHandlingTestFixture isn't supported.

CommandHandlingTest is a so-called Spring Boot Test Slice, which executes the annotated test as Spring Boot test, but with reduced set of beans. The Spring beans (or annotated methods) included in the test context all must reside within classes annotated with CommandHandlerConfiguration.

Mocking

As CommandHandlingTest only recognizes Spring beans defined within CommandHandlerConfigurations supplementary bean dependencies required by any of the CommandHandlerDefinitions or StateRebuildingHandlerDefinitions under test, must be provided manually. As these represent dependent beans, this is preferably achieved using Spring's @MockitoBean annotation, which injects (or replaces) a mock bean into the context. The mock satisfies the bean dependency and may be stubbed and/or verified as part of the test.