Basic Testing with AxonTestFixture

The AxonTestFixture is the main tool for testing Axon applications. It provides a fluent API for setting up test scenarios, executing actions, and verifying results using a clear Given-When-Then pattern.

AxonTestFixture is built on the same ApplicationConfigurer you use in production, which means:

  • No duplicate configuration - Your tests use the same setup as your production code.

  • Integration testing support - Test with real infrastructure (event stores, processors, etc).

  • Consistent behavior - What works in tests works in production.

This makes the AxonTestFixture the choice whenever you want to test components that are attached to Axon.

Creating a test fixture

Basic fixture creation

Create a fixture from any ApplicationConfigurer:

import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule;
import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        EventSourcingConfigurer configurer =
            EventSourcingConfigurer.create()
                                   .registerEntity(EventSourcedEntityModule.autodetected(
                                       String.class, Account.class
                                   ));
        fixture = AxonTestFixture.with(configurer);
    }

    @AfterEach
    void tearDown() {
        fixture.stop();  // Always stop the fixture
    }
    // tests...
}
Always call fixture.stop() in your @AfterEach method to properly release resources.

Fixture with customization

You can customize the fixture using the AxonTestFixture#with(ApplicationConfigurer, UnaryOperator<Customization>) method:

import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule;
import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        EventSourcingConfigurer configurer =
                EventSourcingConfigurer.create()
                                       .registerEntity(EventSourcedEntityModule.autodetected(
                                           String.class, Account.class
                                       ));
        fixture = AxonTestFixture.with(
                configurer,
                customization -> customization.disableAxonServer()  // Disable Axon Server for tests
                                              .registerIgnoredField(MyEvent.class, "timestamp")
                                              .registerIgnoredField(MyEvent.class, "eventId")
        );
    }

    @AfterEach
    void tearDown() {
        fixture.stop();  // Always stop the fixture!
    }
    // tests...
}

The Customization given as the second parameter allows a number of adjustments:

  • disableAxonServer() - Disable Axon Server connector (useful for local testing).

  • registerIgnoredField(Class<?> clazz, String field) - Ignore specific fields during equality checks.

  • registerFieldFilter(FieldFilter filter) - Register custom field filter.

For more details on field filters, be sure to read the dedicated field filters section.

Reusing production configuration

Although you can recreate your configuration specifically for your tests, it is recommended to reuse your production configuration. This ensures your tests use the exact same configuration as production.

  • Configuration API

  • Spring Boot

If you’re not using Axon’s configuration API directly, you can for example create a static configuration method:

// Production configuration class
public class AxonConfig {

    public static EventSourcingConfigurer configurer() {
        return EventSourcingConfigurer.create()
                                      .registerEntity(EventSourcedEntityModule.autodetected(
                                              String.class, Account.class
                                      ))
                                      .registerEntity(EventSourcedEntityModule.autodetected(
                                              String.class, Order.class
                                      ))
                                      .messaging(messaging -> messaging.registerCommandHandlingModule(
                                              CommandHandlingModule.named("payment")
                                                                   .commandHandlers()
                                                                   .annotatedCommandHandlingComponent(c -> new PaymentCommandHandler())
                                      ))
                                      .messaging(messaging -> messaging.eventProcessing(AxonConfig::registerEventHandlers));
    }

    private static EventProcessingConfigurer registerEventHandlers(EventProcessingConfigurer eventProcessing) {
        return eventProcessing.pooledStreaming(pooledConfig -> pooledConfig.processor(
                "my-processor",
                processorConfig -> processorConfig.eventHandlingComponents(components -> components.autodetected(
                        c -> new NotificationEventHandler())
                ).notCustomized()
        ));
    }
}

// Test class
class AccountTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        fixture = AxonTestFixture.with(AxonConfig.configurer());
    }

    @AfterEach
    void tearDown() {
        fixture.stop();  // Always stop the fixture!
    }
    // tests...
}

When using Spring Boot, the configurer is created by the Axon’s Spring Boot integration. Inject it as a bean into your test for realistic integration testing:

import org.axonframework.common.configuration.ApplicationConfigurer;
import org.axonframework.test.fixture.AxonTestFixture;
import org.axonframework.test.server.AxonServerContainer;
import org.junit.jupiter.api.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;

@SpringBootTest
@Testcontainers
class AccountTest {

    @Container
    @ServiceConnection
    static AxonServerContainer axonServer = new AxonServerContainer();

    @Autowired
    private ApplicationConfigurer configurer;

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        fixture = AxonTestFixture.with(configurer);
    }

    @AfterEach
    void tearDown() {
        fixture.stop();
    }
    // Tests ...
}

The @SpringBootTest annotation ensures Spring Boot starts with your production configuration, and @Autowired injects the configurer bean created by Axon’s Spring Boot integration. The AxonServerContainer starts an Axon Server instance in a Docker container for realistic integration testing. The @ServiceConnection annotation (Spring Boot 3.1+) automatically configures the Axon Server connection.

Test phases

Every test follows a three-phase structure:

  1. Given - Set up initial state for your test.

  2. When - Execute the action being tested.

  3. Then - Verify the results of the action.

The API enforces this structure with type-safe phase transitions.

Given-phase: Setting up initial state

The given-phase defines the state before your test action. Each operation runs in its own processing context and is immediately committed.

Given events

You can define the given-phase by defining the events that should form the base for your tests. In most event-driven applications, this is the recommended approach, as it gives granular control of what has happened until you move into the when-phase. The examples below show the provide operations you can use for this:

  • Single event

  • Single event with metadata

  • Multiple events at once

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .event(new MoneyDepositedEvent("account-1", 100.00));
               // when...
    }
}
import org.axonframework.messaging.core.Metadata;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00), Metadata.with("userId", "user-123"));
               // when...
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .events(
                       new AccountCreatedEvent("account-1", 500.00),
                       new MoneyDepositedEvent("account-1", 100.00)
               );
               // when...
    }
}

Note that you can either use the events(…​) method with several events, or invoke the events(…​) method multiple times per event.

Given commands

Besides given events, you are able to derive the initial state based on commands as well. This is useful when you want to test the full command handling flow, or our setup needs both command validation and event generation. The examples below show the provide operations you can use for this:

  • Single command

  • Single command with metadata

  • Multiple commands at once

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .command(new CreateAccountCommand("account-1", 500.00))
               .command(new DepositMoneyCommand("account-1", 100.00));
               // when...
    }
}
import org.axonframework.messaging.core.Metadata;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .command(new CreateAccountCommand("account-1", 500.00), Metadata.with("userId", "user-123"));
               // when...
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .commands(
                       new CreateAccountCommand("account-1", 500.00),
                       new DepositMoneyCommand("account-1", 100.00)
               );
               // when...
    }
}

Note that you can either use the commands(…​) method with several commands, or invoke the command(…​) method multiple times per command.

Given no prior activity

In some scenarios, there are no given events or given commands. When there is no priority activity, you can use the noPriorActivity method to initiate your test scenario:

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .noPriorActivity();
               // when...
    }
}

Given custom execute

For complex setup scenarios you can use execute(Function<Configuration, ?>). The execute() grants access to the entire Configuration, allowing users to access any component they require. This can be useful whenever you need to set up external state, mock (complex) test dependencies, or whenever the fixture does not support what you require.

  • Execute

  • Asynchronous execute

import org.axonframework.modelling.repository.Repository;
import org.axonframework.test.fixture.AxonTestFixture;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .execute(config -> {
                   // Access any component from configuration
                   var repository = config.getComponent(Repository.class);
                   // Perform custom setup
               });
               // when...
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import java.util.concurrent.CompletableFuture;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .executeAsync(config -> {
                   // Return CompletableFuture
                   return CompletableFuture.supplyAsync(() -> {
                       // Async setup logic
                       return null;
                   });
               });
               // when...
    }
}

When-phase: Executing the action

The when-phase executes the action you’re testing. The entire phase runs in a single processing context.

When dispatching commands

In a lot of scenarios, the when-phase triggers by dispatching a command. The command is dispatched through the command bus, and all resulting activity is recorded for verification. The examples below show the provide operations you can use for this:

  • Command

  • Command with metadata

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00));
               // then...
    }
}
import org.axonframework.messaging.core.Metadata;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00), Metadata.with("userId", "user-123"));
               // then...
    }
}

When publishing events

Besides when commands, you are able to trigger the test based on events as well. This is useful when you want to directly test your event handlers or simulate triggered events from an external system). The examples below show the provide operations you can use for this:

  • Single event

  • Single event with metadata

  • Multiple events at once

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .events(new ExternalPaymentReceivedEvent("account-1", 100.00));
               // then...
    }
}
import org.axonframework.messaging.core.Metadata;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .events(
                   new ExternalPaymentReceivedEvent("account-1", 100.00),
                   Metadata.with("userId", "user-123")
               );
               // then...
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .events(
                   new ExternalPaymentReceivedEvent("account-1", 100.00),
                   new PaymentConfirmedEvent("account-1")
               );
               // then...
    }
}

Note that you can either use the events(…​) method with several events, or invoke the events(…​) method multiple times per event.

When nothing happens

In some scenarios, you do not care about some triggering scenario like when commands or when events provides. Thus, when there is nothing happening in the system, you can use the nothing method to move to the then-phase:

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .nothing();
               // then...
    }
}

This option is useful whenever you want to validate against the given state directly, or if you want to validate the base reply from query handlers.

Then-phase: Verifying results

The Then-phase verifies that the action from the when-phase produced the expected results based on the given-phase’s initial state. As with given and when, there are a couple of options for you to validate your system fully. Note that you are able to combine several then-phase verifiers to come to the desired validation!

Then verifying successful handling

If you except the when-phase to proceed without any exceptions, you can use the success() method as a quick validation:

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .success();
    }
}

As stated at the start of this section, you can follow up the success() check with another check, like events: You can combine it with other assertions:

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .success()
               .events(new MoneyWithdrawnEvent("account-1", 100.00));
    }
}

Then verifying exceptional handling

If you except the when-phase to resolve exceptionally, you can utilize a number of exception(…​) methods:

  • Specific exception

  • Specific exception and message

  • Custom exception matcher

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .exception(InsufficientBalanceException.class);
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .exception(InsufficientBalanceException.class, "Insufficient balance");
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .exceptionSatisfies(ex -> {
                   assertInstanceOf(InsufficientBalanceException.class, ex);
                   var ibe = (InsufficientBalanceException) ex;
                   assertEquals("account-1", ibe.accountId());
                   assertEquals(100.00, ibe.requestedAmount());
               });
    }
}

Then verifying events

If the when-phase was based on a command, chances are high you want to verify the events, or events that have been published. To that end, there are a number of event-specific methods you can use. Examples of this are events(…​), eventsSatify(…​), eventsMatch(…​), and noEvents():

  • Single event

  • Multiple events

  • Custom event validation

  • Events match a predicate

  • No events

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .events(new MoneyWithdrawnEvent("account-1", 100.00));
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .events(
                       new MoneyWithdrawnEvent("account-1", 100.00),
                       new BalanceUpdatedEvent("account-1", 400.00)
               );
    }
}

Note that you can either use the events(…​) method with several events, or invoke the events(…​) method multiple times per event.

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .eventsSatisfy(events -> {
                   assertEquals(2, events.size());
                   MoneyWithdrawnEvent firstEvent =
                           events.getFirst().payloadAs(MoneyWithdrawnEvent.class);
                   assertEquals("account-1", firstEvent.accountId());
                   assertEquals(100.00, firstEvent.amount());
               });
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .eventsMatch(events -> events.size() == 2
                       && events.getFirst().payloadAs(MoneyWithdrawnEvent.class)
                                .accountId()
                                .equals("account-1"));
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .noEvents();
    }
}

Then verifying command handling results

If the when-phase was based on a command, you may want to verify the result your command handling functions return. Thus, besides validating the events that have been published, you can for example validate the identifier of newly created entities:

  • Result

  • Result satisfies

  • Result message satisfies

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .noPriorActivity()
               .when()
               .command(new CreateAccountCommand("account-1", 500.00))
               .then()
               .resultMessagePayload("account-1");
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .noPriorActivity()
               .when()
               .command(new CreateAccountCommand("account-1", 500.00))
               .then()
               .resultMessagePayloadSatisfies(
                       String.class,
                       result -> result.equals("account-1")
               );
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .noPriorActivity()
               .when()
               .command(new CreateAccountCommand("account-1", 500.00))
               .then()
               .resultMessageSatisfies(
                       result -> result.payloadAs(String.class).equals("account-1")
               );
    }
}

Then verifying dispatched commands

Actions within the when-phase can result in additional commands to be dispatched. For example, if an event handler triggers the next step in a workflow, you would need to validate that new command. To that end, you can use a number of command-specific then-phase tasks to validate your application. Examples of this are commands(…​), commandsSatify(…​), commandsMatch(…​), and noCommands():

  • Single command

  • Multiple commands

  • Custom command validation

  • Commands match a predicate

  • No commands

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .commands(new SendEmailCommand("user@example.com", "Welcome!"));
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .commands(
                       new SendEmailCommand("user@example.com", "Welcome!"),
                       new DeregisterAccountCommand("account-1")
               );
    }
}

Note that you can either use the commands(…​) method with several commands, or invoke the commands(…​) method multiple times per command.

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .commandsSatisfy(commands -> {
                   assertEquals(1, commands.size());
                   SendEmailCommand command = commands.getFirst().payloadAs(SendEmailCommand.class);
                   assertEquals("user@example.com", command.recipient());
               });
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .commandsMatch(commands -> commands.size() == 1
                       && commands.getFirst().payloadAs(SendEmailCommand.class)
                                  .equals("user@example.com"));
    }
}
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .noCommands();
    }
}

Then await verification

Sometimes, the when-phase action will not trigger immediately. Examples of these are (integration) tests using a streaming event processor, which by design take some time. When this is the case you can use the await(…​) operation. If you do not provide a Duration, the task will be awaited on for a default of five seconds. When you know a task should be verifiable sooner or later, you can define your own Duration:

  • Await

  • Await with duration

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .await(then -> then.commands(new SendEmailCommand("user@example.com", "Welcome!")));
    }
}

Using plain await(…​) uses the default timeout of 5 seconds at all time.

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import java.time.Duration;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .await(
                       then -> then.commands(new SendEmailCommand("user@example.com", "Welcome!")),
                       Duration.ofMillis(250)
               );
    }
}

Then custom expect verification

For complex verification scenarios you can use expect(Consumer<Configuration>). The expect() grants access to the entire Configuration, allowing users to access any component they require. This can be useful whenever you need to verify external state, validate custom components, or whenever the fixture does not support what you require.

  • Expect

  • Asynchronous expect

import org.axonframework.messaging.queryhandling.gateway.QueryGateway;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .expect(config -> {
                   AccountBalance balance =
                           config.getComponent(QueryGateway.class)
                                 .query(
                                         new GetBalanceQuery("account-1"),
                                         AccountBalance.class
                                 )
                                 .join();
                   assertEquals(400.00, balance.amount());
               });
    }
}
import org.axonframework.messaging.queryhandling.gateway.QueryGateway;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

import static org.junit.jupiter.api.Assertions.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               // ...
               .when()
               .event(new AccountClosedEvent("account-1"))
               .then()
               .expectAsync(config -> {
                   QueryGateway queryGateway = config.getComponent(QueryGateway.class);
                   // Return CompletableFuture, like the result from the QueryGateway
                   return queryGateway.query(
                                              new GetBalanceQuery("account-1"),
                                              AccountBalance.class
                                      )
                                      .thenAccept(balance -> assertEquals(400.00, balance.amount()));
               });
    }
}

Chaining multiple tests

When constructing unit tests sticking to a single given-when-then flow is recommended. However, some test scenarios benefit from validating a more intricate flow.

Workflows are a clear example of this, where a number of given events, followed by a command, result in events that form a clear basis for a follow-up command. To support these cases you can use the and() operation to chain an additional given-when-then block:

import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountTest {

    private AxonTestFixture fixture;

    @Test
    void test() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .success()
               .events(new MoneyWithdrawnEvent("account-1", 100.00))
               .and()  // Chain to next test
               .when()
               .command(new WithdrawMoneyCommand("account-1", 500.00))
               .then()
               .exception(InsufficientBalanceException.class)
               .noEvents();
    }
}

This structure provides a number of benefits you can utilize. Like being able to reuse the previous test’s state, validating a sequence of operations, or to verify your workflows are up to par.

Example test scenarios

Besides the specific examples spread throughout the explanation of the different phases, there are coarse grained examples that you can benefit from. To that end, the list below shows some tests for command handling components, event handling components, event-sourced entities, and testing query handling components.

Example: Testing a stateful command handling component

In this test, the user decided to have a Command Handling Component called the AccountCommandHandler for which the state is automatically injected in the command handlers. The state, which is the event-sourced Account, is registered separately with the configurer in this example. Furthermore, the developer decided to construct fine-grained unit tests to validate the flow:

import org.axonframework.common.configuration.ModuleBuilder;
import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule;
import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.messaging.commandhandling.configuration.CommandHandlingModule;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountCommandHandlerTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        EventSourcedEntityModule<String, Account> eventSourcedEntity =
                EventSourcedEntityModule.autodetected(String.class, Account.class);
        ModuleBuilder<CommandHandlingModule> accountCommandHandlingModule =
                CommandHandlingModule.named("account")
                                     .commandHandlers()
                                     .annotatedCommandHandlingComponent(c -> new AccountCommandHandler());
        EventSourcingConfigurer configurer =
                EventSourcingConfigurer.create()
                                       .registerEntity(eventSourcedEntity)
                                       .messaging(messaging -> messaging.registerCommandHandlingModule(
                                               accountCommandHandlingModule
                                       ));

        fixture = AxonTestFixture.with(configurer);
    }

    @Test
    void createAccount() {
        fixture.given()
               .noPriorActivity()
               .when()
               .command(new CreateAccountCommand("account-1", 500.00))
               .then()
               .success()
               .events(new AccountCreatedEvent("account-1", 500.00));
    }

    @Test
    void withdrawMoney() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .success()
               .events(new MoneyWithdrawnEvent("account-1", 100.00));
    }

    @Test
    void withdrawInsufficientBalance() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 50.00))
               .when()
               .command(new WithdrawMoneyCommand("account-1", 100.00))
               .then()
               .exception(InsufficientBalanceException.class)
               .noEvents();
    }

    @AfterEach
    void tearDown() {
        fixture.stop();
    }
}

Example: Testing an event handling component

Here is an example test scenario how an Event Handling Component sends emails as a result of handling an event. Take note of the use of await, which mandatory to give the event processor time to actually process the event.

import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.eventhandling.configuration.EventProcessorModule;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorConfiguration;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorModule;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class NotificationEventHandlerTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        MessagingConfigurer configurer = MessagingConfigurer.create().eventProcessing(
                processing -> processing.pooledStreaming(
                        pooledStreaming -> pooledStreaming.processor(
                                "notifications",
                                NotificationEventHandlerTest::configurePooledProcessor))
        );

        fixture = AxonTestFixture.with(configurer);
    }

    private static PooledStreamingEventProcessorModule configurePooledProcessor(
            EventProcessorModule.EventHandlingPhase<PooledStreamingEventProcessorModule, PooledStreamingEventProcessorConfiguration> processor
    ) {
        return processor.eventHandlingComponents(components -> components.autodetected(
                                c -> new NotificationEventHandler())
                        )
                        .notCustomized();
    }

    @Test
    void sendNotificationOnAccountCreated() {
        fixture.given()
               .noPriorActivity()
               .when()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .then()
               .await(
                       t -> t.success()
                             .commands(new SendEmailCommand(
                                     "user@example.com", "Your account has been created"
                             ))
               );
    }

    @Test
    void sendNotificationOnLowBalance() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .when()
               .event(new MoneyWithdrawnEvent("account-1", 480.00))
               .then()
               .await(
                       t -> t.success()
                             .commands(new SendEmailCommand(
                                     "user@example.com", "Low balance alert"
                             ))
               );
    }

    @AfterEach
    void tearDown() {
        fixture.stop();
    }
}

Example: Testing an event-sourced entity with command handler

In this test example, the developers decided to combine the command handler with the event-sourced entity in one object. Furthermore, the test runs through the complete lifecycle of an account in one go using the fixture’s chaining capabilities:

import org.axonframework.eventsourcing.configuration.EventSourcedEntityModule;
import org.axonframework.eventsourcing.configuration.EventSourcingConfigurer;
import org.axonframework.test.fixture.AxonTestFixture;
import org.junit.jupiter.api.*;

class AccountEntityTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        EventSourcingConfigurer configurer =
                EventSourcingConfigurer.create().registerEntity(
                    EventSourcedEntityModule.autodetected(String.class, Account.class)
                );

        fixture = AxonTestFixture.with(configurer);
    }

    @Test
    void accountLifecycleReactsAsExpected() {
        fixture.given()
               .noPriorActivity()
               .when()
               .command(new CreateAccountCommand("account-1", 500.00))
               .then()
               .success()
               .events(new AccountCreatedEvent("account-1", 500.00))
               .and()
               .when()
               .command(new DepositMoneyCommand("account-1", 100.00))
               .then()
               .success()
               .events(new MoneyDepositedEvent("account-1", 100.00))
               .and()
               .when()
               .command(new WithdrawMoneyCommand("account-1", 200.00))
               .then()
               .success()
               .events(new MoneyWithdrawnEvent("account-1", 200.00))
               .and()
               .when()
               .command(new CloseAccountCommand("account-1"))
               .then()
               .success()
               .events(new AccountClosedEvent("account-1"));
    }

    @AfterEach
    void tearDown() {
        fixture.stop();
    }
}

Example: Testing a query handling component

As the test fixtures (not yet) support query validation, this scenario uses the expect(…​) operation to fully customize the then-phase to their purposes:

import org.axonframework.common.configuration.ModuleBuilder;
import org.axonframework.messaging.core.configuration.MessagingConfigurer;
import org.axonframework.messaging.eventhandling.configuration.EventProcessorModule;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorConfiguration;
import org.axonframework.messaging.eventhandling.processing.streaming.pooled.PooledStreamingEventProcessorModule;
import org.axonframework.messaging.queryhandling.configuration.QueryHandlingModule;
import org.axonframework.messaging.queryhandling.gateway.QueryGateway;
import org.axonframework.test.fixture.AxonTestFixture;

import static org.junit.jupiter.api.Assertions.*;

class AccountQueryHandlerTest {

    private AxonTestFixture fixture;

    @BeforeEach
    void setUp() {
        ModuleBuilder<QueryHandlingModule> accountQueryHandlingModule =
                QueryHandlingModule.named("account")
                                   .queryHandlers()
                                   .annotatedQueryHandlingComponent(c -> new AccountQueryHandler());
        MessagingConfigurer configurer =
                MessagingConfigurer.create()
                                   .registerQueryHandlingModule(accountQueryHandlingModule)
                                   .eventProcessing(
                                           processing -> processing.pooledStreaming(
                                                   pooledStreaming -> pooledStreaming.processor(
                                                           "notifications",
                                                           AccountQueryHandlerTest::configurePooledProcessor
                                                   )
                                           )
                                   );

        fixture = AxonTestFixture.with(configurer);
    }

    private static PooledStreamingEventProcessorModule configurePooledProcessor(
            EventProcessorModule.EventHandlingPhase<PooledStreamingEventProcessorModule, PooledStreamingEventProcessorConfiguration> processor
    ) {
        return processor.eventHandlingComponents(components -> components.autodetected(
                                c -> new NotificationEventHandler())
                        )
                        .notCustomized();
    }

    @Test
    void testGetAccountBalance() {
        fixture.given()
               .event(new AccountCreatedEvent("account-1", 500.00))
               .event(new MoneyDepositedEvent("account-1", 100.00))
               .when()
               .nothing()
               .then()
               .expect(config -> {
                   QueryGateway queryGateway = config.getComponent(QueryGateway.class);
                   var balance = queryGateway.query(
                           new GetBalanceQuery("account-1"),
                           AccountBalance.class
                   ).join();
                   assertEquals(600.00, balance.amount());
               });
    }

    @AfterEach
    void tearDown() {
        fixture.stop();
    }
}