Testing Command Handlers
As our application grows, we won’t be able to manually test that everything works correctly after a new feature. It is convenient to have a way to test automatically that our application works as expected.
In your applications, you probably already have some unit tests to check the business logic of your application.
Axon Framework provides you with a Test Module that will help you write unit tests designed to test the processing and handling of commands, events and queries in your application.
In this tutorial step, you will implement a unit test to check if our system processes the RegisterBikeCommand
as expected.
Configuring Axon Framework test support
Before writing our command handler test, we need to add the component Axon Framework provides to support unit testing.
We need to add the org.axonframework:axon-test
dependency to the Maven file descriptor in our rental
module to do this. Fortunately, we already added this dependency when we configured the Axon Framework dependencies.
Check that your rental/pom.xml
file contains the reference to the axon-test
module:
<dependencies>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.axonframework</groupId>
<artifactId>axon-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
The components provided by Axon Framework for unit testing are only needed during the test execution phase during the maven build process. Thus, we specify the <scope>test</scope> for the axon-test dependency.
|
With the axon-test
component added to our project’s dependency list we can create our command handler test.
Creating the command BikeTest
To start writing our command handler test, create a new Java class with the name BikeTest
in /src/test/java/io/axoniq/demo/bikerental/rental/command
of your rental
module.
You can also use your IDE to create the unit test class. Open the Bike class and ask your IDE to generate the corresponding unit test. Depending on your IDE, the shortcut or menu may vary, but it’s a shortcut worth knowing for your IDE.
|
class BikeTest {
}
Defining the AggregateTestFixture
Axon Framework provides a component that allows you to create unit tests specifically focused on testing the behavior of handling a command by an aggregate. This component is called AggregateTestFixture
.
You can read more details about how an AggregateTestFixture works at the Command Model Testing |
So, we need to add and initialize the AggregateTestFixture
for the Bike
class (the component that handles the command we want to test):
class BikeTest {
private AggregateTestFixture<Bike> fixture; (1)
@BeforeEach (2)
void setUp() {
fixture = new AggregateTestFixture<>(Bike.class); (3)
}
}
1 | Define an AggregateTestFixture for the Bike aggregate. This is the "Subject Under Test" (or SUT) for our test. |
2 | The @BeforeEach marks this method to be called before any test is executed in our test class. Adding the code to create the AggregateTestFixture here will ensure that we have a fresh fixture for each test case, and thus we make our different tests independent. |
3 | This line creates a new AggregateTestFixture for our Bike class. |
Testing the command handler
Thanks to the help of the AggregateTestFixture
we can now create a test with the following structure:
-
Given: Set the initial state for our test. Since we are designing our system to follow Event-Sourcing patterns, we need to set the list of events that have already happened for the same aggregate (the bike) before receiving the command.
-
When: Specify the command whose execution we want to test. In this case, we will test the processing of a
RegisterBikeCommand
. -
Expect: We can instruct the fixture on the expectations we have from our system after processing the command. In an Event-Sourcing system, we will specify these expectations in the form of what events should have been produced by the command handler as a result of processing the command.
So, let’s define a method in our unit test to check that our system can successfully process the request to create a bike:
class BikeTest {
@Test
void canRegisterBike() {
fixture.givenNoPriorActivity() (1)
.when(new RegisterBikeCommand("bikeId-1234", "city-bike", "Amsterdam")) (2)
.expectEvents(new BikeRegisteredEvent("bikeId-1234", "city-bike", "Amsterdam")); (3)
}
}
1 | In our case, when we receive the RegisterBikeCommand , we expect that no previous events were received in the system for the same bikeId . |
2 | We provide the RegisterBikeCommand we want to send to the command handler. |
3 | After successfully processing the RegisterBikeCommand , we expect the command handler to produce a new BikeRegisteredEvent with the details of the new bike. |
The AggregateTestFixture
will prepare the Bike aggregate to execute the command (in this case, this step is empty, as we specified that there had been no previous activity), execute the command handler for the RegisterBikeCommand
and assert that the command handler has emitted the BikeRegisteredEvent
with these specific values.
You can learn more about the different things you can check from the fixture using Matchers in the section dedicated to Validation-phase of the test fixture in the Axon Framework reference guide
|
Executing the test
You can run the test manually from your IDE, and you should see that the test passes, meaning that the fixture has checked that the expectations are met after processing the RegisterBikeCommand
.
In addition to running the test manually, we now have a test that is automatically run by Maven every time we build the application. If you run mvn package
from your command line, you will see the execution of the test:
% mvn package
[...]
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running io.axoniq.demo.bikerental.rental.command.BikeTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.120 s -- in io.axoniq.demo.bikerental.rental.command.BikeTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[...]
This automatic execution of the tests guarantees that if we break the expected behavior for this command handling while implementing a new feature in the future, we will know about it immediately.
With that certainty, we can move on and implement the next feature for our system.