Test-First Development using Axon Framework

Christian Babsek
Digital Frontiers — Das Blog
9 min readFeb 5, 2021

Testing your software is crucial. Thinking about the complexity of software products nowadays, nobody can ever understand the whole system. And even very experienced software developers are very used to see some of their tests “getting red”, even if they are in a part of the code that has absolutely nothing to do with where you changed your code. Nobody’s perfect, that’s the business. So having tests for your whole software is always considered a best practice but can often feel very cumbersome, especially in classical software products with layered architectures. Try to think about tests for the data mapping layer, the service layer, the controller layer and so on. You could have your business logic within all those layers and sometimes end up mocking other layers in a way that makes your code just testing what you mocked before. This is where event sourcing comes into play.

The relevant key idea of Event Sourcing

The event order is important — and might tell different stories

A very important idea of event sourcing is, that events are always the single source of truth. This means, that the complete state of a technical system can be described as a sequence of events. All relevant things that can change the state must be described as an event. The order of events is crucial. While there might be some simple cases in which the event order has no actual effect and does not change the outcome, in most cases the order is crucial. Getting events in exact order is what makes your event sourcing handlers work as they should.

Our example: a warehouse aggregate

The modeled warehouse contains different products (each identical) of various amount.

Imagine you are operating an online shop selling and shipping products.
In this example, you could think of an aggregate called WarehouseAggregate. Typical business cases would be to add products to the store, to increase or decrease the stock but also to ensure that new products get ordered when the stock falls under a specified level. But how to ensure that things work exactly how they should? How to know if the automatic reordering of products also works when the stock falls 2 below the minimum? And how to prove that your business logic is resilient enough to handle exceptions and external errors? This is where unit tests become part of the game. Let us have a first look at what Axon Framework provides.

The Axon Framework

The Axon Framework is a free-to-use enterprise framework for Event Sourcing and Domain Driven CQRS micro-services written in Java. It offers many ways to very flexibly write your software using these paradigms. Axon is also 100% compatible with frameworks like Spring and supports, despite of its own-developed Axon Server, also simple configurations using SQL databases. One of the main advantages with Axon is the possibility to use a very high level programming model that wires simple POJOs just by using of a few powerful annotations. Some of them are:

  • @Aggregate Define the concrete Aggregate class that receives the commands and sources the events.
  • @CommandHandler A command handler that receives command objects to proceed the actual business logic.
  • @EventSourcingHandler Define the event sourcing handlers which are responsible to reproduce the current state from all previously applied events.

The Aggregate Test Fixture

The Axon Framework makes testing of business logic blindingly easy using the bundled AggregateTestFixture. The test fixture lets you write your tests in three parts, the so called Gherkin syntax commonly known as Given-When-Then.

The Axon test fixture consists of a given, a when and a then part

The Given part

At first, you define the preconditions, the so called given part. Considering that in Event Sourcing events are the single source of truth, you can create every possible state of your system from a sequence of events. The given part usually consists out of zero, one or multiple events. Its also possible to use commands due to the reason that commands do nothing more than emitting events in their associated event handler code.

The When part

The second part of the fixture is the when part. Here you define, which command you want to send to the aggregate. This will be your concrete business unit to test.

The Then part

The third and last part is the then part. Here you assert that something specific happened as a consequence of your command. Typical expectations are simply nothing (the command arrived but no event was emitted), one or multiple events got emitted including their type and content or last but not least: An exception was thrown, which would probably get handled by the calling component that has sent the command.

A first example

Lets try to imagine you simply want to process an order. Your customer ordered a product with the productId P001, you still have enough of them to serve and want to prove, that after it got taken out of the warehouse, the stock decreased by 1. Nothing easier than that.

At first, you should probably ensure that your warehouse contains enough of the requested product. Let us add 3 items of our product in the given-clause:

The given part allows 0, 1 or multiple events that are the history of things (events) that happened before. Events are the only way to change the state.

After this point, we suppose (and if there are already test cases for this, we know) our warehouse to contain three items of the product P001 in it. Now lets come to our expectation. We want to ensure, that it is possible, to take one of them out, so the next part of the test is our definition of the when part.

Here we want to define, which command we consider for our test. We want to define something to happen, when the TakeProductCommand gets called on an aggregate in the state that we defined in the given part before.

And this is what we expect to happen: We expect a ProductStockDecreasedEvent that is specific for our warehouse warehouse01. It should “say”, that the stock of our product P001 has decreased, in this example it says, that its stock changed from 3 to 2. Lets summarize this test in one sentence:

  • Given a Warehouse warehouse01, containing the Product P001 with amount of 3 in it, so when
  • When there is an request to take the product P001 out of warehouse01 with amount of 1
  • Then expect this handler execution to be successful and our expected consequence is an event, saying, that the product stock for warehouse01 of product P001 has decreased from 3 to 2.

That’s it.

What if an exception occurs?

Despite the possibility to ensure that your test ends with no exceptions thrown, using .expectSuccessfulHandlerExecution(), sometimes having an exception thrown is exactly the thing you want. For this case, the test fixture provides the methods .expectException(Throwable) and .expectException(Matcher), that allow to verify that a specific exception was thrown, either by its type (e.g. WarehouseNotEmptyException) or by a given Hamcrest matcher that asserts the exception to have given fields or in general: To use arbitrary assertion logic on it.

Events Matching

Sometimes you don’t want to say exactly what events you are expecting. Think about having events that contain some randomly generated value or let it be just the case that you are too lazy (let’s hope you are not ;-) ) to write every single event expectation inside your test. For this, the Axon Framework provides various ways to define your expectations:

  • Simply match on the whole event body
  • Match by Event Type
  • Write custom matchers, which also makes it possible to just assert a non-strict order of events allowing gaps in between
  • Match additional information like event metadata

More informations about available matchers in Axon can be found here. Its also possible to write some of them on your own.

Another way to verify: assertState()

There is another way to verify the result of your test. Generally, its not recommended to do this, because it makes your black box design getting more transparent than wanted, it might still help to ensure that your aggregate (and its sub entities) is in a specific state. Generally, the best way to do this is using events, but there might be situations that make it easier to ensure a specific state. This is where you can use assertState() as a way to write a matcher method that verifies arbitrary conditions for the whole aggregate object. Within this lambda block all other assertions are possible, even iterating through sub entities, counting and all other things that can be described in code.

Working with external resources

Sometimes, your aggregate uses injected resources. Try to imagine a CredentialsService, that initializes and verifies user credentials or also other kind of external resources and devices, that you want to use within your aggregate. In this case, you will get an error whenever you call your Axon tests:

org.axonframework.test.FixtureExecutionException: No resource of type […] has been registered. It is required for one of the handlers being executed.

To avoid this, there is a special way to configure your AggregateTestFixture in advance. One of them is the registerInjectableResource method that you can call on it and define a mock or stub implementation, that will be injected in your command handler methods and also helps you to verify calls to them.

The Kotlin language offers a very elegant way to provide the external resources by using the “lazy delegate” as seen in the next snippet:

Creating the test fixture using lazy initialization for the test fixture. This makes it possible to provide also preconfigured service stubs or mocks that the fixture then can inject for each test individually. A typical way to provide them would be for example the MockKExtension.

Test first

Sometimes you want to write a software and don’t know for sure if it fulfills all your requirements. If you find them, the next step can be writing interfaces for your business classes, defining the signatures of methods but not writing actual code. Writing tests first often sounds easier than it is. Especially in strictly typed languages like Java or Kotlin, it might be necessary to write larger skeletons of code that still has no real working code at all. This is where Event Sourcing makes things easier again. Especially in situations with activities like Event Storming before starting the project, you often know exactly what things “can happen”. And you also know: Things that can happen must be described as events. So “test first” in Axon is nothing more than creating your event and command classes, creating an empty aggregate class and writing your tests. And when you go on to actually code the business code, it will be clear: All tests green means all features complete.

You often hear that the “tests first” principle is nothing more than an academical theory, that won’t ever work in real. But believe me: From my daily work as a software engineer, writing tests first makes things easier. I can really encourage everyone to try it. There might be things where it makes no sense, but Event Sourcing and Axon are definitely a very good fit. The reason for this is that your only “public api”, the only way to actually change things, is the command layer. So tests first with Axon means nothing more than writing tests out of previous events, actual commands and the consequences, more events.

Summary

In conclusion, the AxonTestFixture makes testing your business logic quite easy. The biggest advantage should be that you are testing a black box. Your command layer is nothing less than the public api for your domain. There are no other ways to change the state than with commands (emitting events as the materialized state of the system), which makes it possible to test the whole business logic. You can find a working demonstration project that contains all the tests available in the article here.

What else is possible?

Testing the Command layer and the event sourcing is not the only thing to do. Despite the also existing SagaTestFixture for the handling of so-called SAGAs, testing other components like the Event Handlers, the Query Components and other parts of the system, there are dozens of things to know. These questions could also become part of future articles here on Medium. If you are interested in more things as well as for feedback and questions, don’t hesitate to contact me or write a comment below. Thank you very much for reading my first article here on Digital Frontiers Blog.

What’s more to know

--

--

Christian Babsek
Digital Frontiers — Das Blog

Software Developer based in Frankfurt, Germany, specialized in Kotlin, Spring Framework, CQRS and Event Sourcing, Test-driven development and code quality