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
EsdbClientis 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
Eventinformation is stubbed, as no event store is involved, see the JavaDoc ofCommandHandlerDefinition. - 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
)
);
}
- initialization omitted for brevity
- initializes the fixture with an empty state, i.e. no prior events published
- executes
PurchaseBookCommandcommand handler, capturing any newly published events - verifies successful command handler execution
- verifies that exactly one
BookPurchasedEventwas 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)! */);
// ...
}
- Variable number of
StateRebuildingHandlerDefinitioninstances. Could be omitted for this test, sincePurchaseBookCommandis a creational command. - The
CommandHandlerDefinitionto 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
)
);
}
}
- It is important to use the unambiguous generic command type, which uniquely identifies the autoconfigured fixture. Raw usage of
CommandHandlingTestFixtureisn'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.