axon-test
module, which can be obtained by configuring a maven dependency (use <artifactId>axon-test</artifactId>
and <scope>test</scope>
) or from the full package download.AggregateTestFixture
allows you to configure a certain infrastructure, composed of the necessary command handler and repository, and express your scenario in terms of "given-when-then" events and commands.Focus of a Test FixtureSince the unit of testing here is the aggregate,AggregateTestFixture
is meant to test one aggregate only. So, all commands in thewhen
(orgiven
) clause are meant to target the aggregate under test fixture. Also, allgiven
andexpected
events are meant to be triggered from the aggregate under test fixture.
GiftCard
aggregate (as defined earlier):Aggregate Lifecycle ExtensionAlternatively, JUnit 5 users can useStubAggregateLifecycleExtension
in order to directly attach to the aggregate lifecycle. This might be useful if you prefer not to use theAggregateTestFixture
1public class AggregateTest {2​3@RegisterExtension4static StubAggregateLifecycleExtension testSubject = new StubAggregateLifecycleExtension();5​6@Test7void test() {8apply(new Event(...));9​10assertEquals(1, testSubject.getAppliedEvents().size());11}1213}Copied!
FixtureConfiguration
, TestExecutor
and ResultValidator
, respectively.Fluent InterfaceTo make optimal use of the migration between these stages, it is best to use the fluent interface provided by these methods, as shown in the example above.
registerAnnotatedCommandHandler
method. Besides an Annotated Command Handler, you can register a wide variety of components and settings that define how the infrastructure around the Aggregate's test should be set up, consisting out of the following:registerRepository
:registerRepositoryProvider
:registerAggregateFactory
:registerAnnotatedCommandHandler
:registerCommandHandler
:MessageHandler
of CommandMessage
.registerInjectableResource
:registerParameterResolverFactory
:ParameterResolvers
with custom ParameterResolver
.registerCommandDispatchInterceptor
:registerCommandHandlerInterceptor
:registerDeadlineDispatchInterceptor
:registerDeadlineHandlerInterceptor
:registerFieldFilter
:Field
filter used when comparing objects in the "then" phase.registerIgnoredField
:registerHandlerDefinition
:registerHandlerEnhancerDefinition
:HandlerEnhancerDefinition
with a custom HandlerEnhancerDefinition
.registerCommandTargetResolver
:CommandTargetResolver
to the test fixture.DomainEventMessage
s. If the "given" event implements Message
, the payload and meta data of that message will be included in the DomainEventMessage
, otherwise the given event is used as payload. The sequence numbers of the DomainEventMessage
are sequential, starting at 0
. If no prior activity is expected, the givenNoPriorActivity()
can be used as the starting point.givenCommands(...)
" method to provide command objects.fixture.givenState(() -> new GiftCard())
to define the initial state.CommandMessage
it will be dispatched as is. The behavior of the invoked handler (either on the aggregate or as an external handler) is monitored and compared to the expectations registered in the validation phase.whenThenTimeElapses(Duration)
and whenThenTimeAdvancesTo(Instant)
handles. These support testing the publication of DeadlineMessages
, as is further defined in this chapter.Illegal State Change DetectionDuring the execution of the test, Axon attempts to detect any illegal state changes in the aggregate under test. It does so by comparing the state of the aggregate after the command execution to the state of the aggregate if it sourced from all "given" and stored events. If that state is not identical, this means that a state change has occurred outside of an aggregate's event handler method. Static and transient fields are ignored in the comparison, as they typically contain references to resources.You can switch detection in the configuration of the fixture with thesetReportIllegalStateChange()
method.
fixture.expectSuccessfulHandlerExecution()
:fixture.expectResultMessagePayload(Object)
:fixture.expectResultMessagePayloadMatching(Matcher)
:fixture.expectResultMessage(CommandResultMessage)
:CommandResultMessage
received has equal payload and meta data to that of given message.fixture.expectResultMessageMatching(Matcher)
:CommandResultMessage
matches the given Matcher.fixture.expectException(Matcher)
:Matcher
.fixture.expectException(Class)
:fixture.expectExceptionMessage(String)
:fixture.expectExceptionMessage(Matcher)
:equals()
) with their counterparts in the actual Events. If one of the properties is not equal, the test fails and an extensive error report is generated.Matcher
is an interface prescribing two methods: matches(Object)
and describeTo(Description)
. The first returns a boolean to indicate whether the matcher matches or not. The second allows you to express your expectation. For example, a "GreaterThanTwoMatcher" could append "any event with value greater than two" to the description. Descriptions allow expressive error messages to be created about why a test case fails.Matchers
utility class.Matchers.listWithAllOf(event matchers...)
Matchers.listWithAnyOf(event matchers...)
Matchers.sequenceOf(event matchers...)
Use this matcher to verify that the actual events are matched in the same order as the provided event matchers. It will succeed if each matcher matches against an event that comes after the event that the previous matcher matched against. This means that "gaps" with unmatched events may appear.null
". It is up to the event matchers to decide whether they accept that or not.Matchers.exactSequenceOf(event matchers...)
Matchers.equalTo(instance...)
Matchers.andNoMore()
or Matchers.nothing()
null
value.Matchers.matches(Predicate)
or Matchers.predicate(Predicate)
Predicate
.Predicate
API provides a better means to validating the outcome.Matchers.messageWithPayload(payload matcher)
Matchers.payloadsMatching(list matcher)
expectState
method takes a consumer of the Aggregate type. Use regular assertions provided by your test framework to assert the state of the given Aggregate. Any (Runtime) Exception or Error will fail the test case accordingly.Event-Sourced Aggregate State ValidationState validation for testing Event Sourced Aggregates is considered bad practice. Ideally, the state of an Aggregate is completely opaque to the testing code, as only the behavior should be validated. Generally, the desire to validate state is an indication that a certain test scenario is missing from the test suite.
Duration
or an Instant
, using explicit equals, a Matcher
or just a deadline type to verify the deadline message.
The following methods are available for validating Deadlines:expectScheduledDeadline(Duration, Object)
:deadline
to be scheduled after the specified Duration
.expectScheduledDeadlineMatching(Duration, Matcher)
:Matcher
to be scheduled after the specified Duration
.expectScheduledDeadlineOfType(Duration, Class)
:Duration
.expectScheduledDeadlineWithName(Duration, String)
:Duration
.expectScheduledDeadline(Instant, Object)
:deadline
to be scheduled at the specified Instant
.expectScheduledDeadlineMatching(Instant, Matcher)
:Matcher
to be scheduled at the specified Instant
.expectScheduledDeadlineOfType(Instant, Class)
:Instant
.expectScheduledDeadlineWithName(Instant, String)
:Instant
.expectNoScheduledDeadlines()
:expectNoScheduledDeadlineMatching(Matcher)
:Matcher
to be scheduled.expectNoScheduledDeadlineMatching(Duration, Matcher)
:Matcher
to be scheduled after the specified Duration
.expectNoScheduledDeadline(Duration, Object)
deadline
to be scheduled after the specified Duration
.`expectNoScheduledDeadlineOfType(Duration, Class)
Duration
.`expectNoScheduledDeadlineWithName(Duration, String)
Duration
.`expectNoScheduledDeadlineMatching(Instant, Matcher)
:Matcher
to be scheduled at the specified Instant
.expectNoScheduledDeadline(Instant, Object)
deadline
to be scheduled at the specified Instant
.`expectNoScheduledDeadlineOfType(Instant, Class)
Instant
.`expectNoScheduledDeadlineWithName(Instant, String)
Instant
.`expectDeadlinesMet(Object...)
:deadline
or several deadlines to have been met.expectDeadlinesMetMatching(Matcher<List<DeadlineMessage>>)
: