Introduce a test isolation strategy based on event tags#4334
Open
theoema wants to merge 2 commits intoAxonIQ:mainfrom
Open
Introduce a test isolation strategy based on event tags#4334theoema wants to merge 2 commits intoAxonIQ:mainfrom
theoema wants to merge 2 commits intoAxonIQ:mainfrom
Conversation
Contributor
Author
|
I'm sure you're all very very busy prepping for the 5.1.0 release, looks like its coming out any time now, so seriously don't worry about reviewing this unless you have the time and want to! |
Contributor
|
Hey @theoema, this looks like a great addition - i've added it to the 5.2.0 milestone and we'll be sure to have a more detailed look once 5.1.0 is out. I've seen there are test failures in the SpringBoot Starter Test module you could check out meanwhile if you don't mind. |
- Add TestIsolationEnhancer that enables test isolation by stamping messages with a unique testId, persisting it as event tags, and filtering sourcing reads and recording assertions - Add IsolatingCommandBus and IsolatingEventSink that stamp outgoing messages with testId metadata - Add IsolatingEventStore and IsolatingEventStoreTransaction that adjust sourcing criteria to include the testId tag - Add IsolatingRecordingCommandBus and IsolatingRecordingEventSink that filter recorded messages by testId for assertions - Add FixtureCustomizer interface as a per-fixture extension point invoked once per TestContext.create() - Add FixtureConfiguration record as the narrow input/output type for FixtureCustomizer (commandBus, eventSink, recordings) - Add TestContext as a per-test context object that holds resolved fixture components and flows through the phase chain (Given → When → Then → and()), ensuring stable test identity across chained phases - Fix and() re-invoking FixtureCustomizer and generating a new testId by reusing the same TestContext via a package-private AxonTestFixture constructor - Replace 6 individual fields in AxonTestFixture with single TestContext field - Simplify all phase constructors (AxonTestGiven, AxonTestWhen, AxonTestThenMessage, AxonTestThenCommand, AxonTestThenEvent, AxonTestThenNothing) from 4-7 parameters to a single TestContext parameter - Add unit tests for all isolation components and an integration test validating two fixtures sharing a configuration see isolated events - Update AxonTestFixtureStatefulCommandHandlerTest and TestIsolationIntegrationTest for upstream EventSourcingRepository constructor change (Snapshotter parameter)
…nstructor - Fix AxonSpringBootTestAnnotationTest reflection to navigate through testContext field instead of the removed customization field - Migrate EventSourcingRepository usage from deprecated CriteriaResolver constructor to new SimpleSourcingHandler constructor in AxonTestFixtureStatefulCommandHandlerTest and TestIsolationIntegrationTest
7fde761 to
db1fdd1
Compare
Contributor
Author
|
Hey @hatzlj, thanks for taking a peek, I rebased off main and adjusted some stuff to new APIs and fixed the broken tests, have a good weekend! |
|
@theoema great addition, keep up the good work! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This implements the test isolation strategy I proposed in #4252. The idea is simple: each
AxonTestFixturegets a unique UUID stamped on every message as metadata, persisted as a tag on stored events, and used to filter sourcing reads and recording assertions. This means multiple fixtures can share the same configuration and event store without cross-test interference — no purging, no cleanup, and safe for parallel execution.It's fully opt-in. You register the
TestIsolationEnhanceron your configurer and everything else is handled transparently:Each fixture is isolated — events from one test are invisible to another, even though they share the same event store.
How it works
The
TestIsolationEnhancerregisters four pieces of infrastructure:CorrelationDataProviderthat propagates the testId through the message chainTagResolverdecorator that converts testId metadata into aTagon stored eventsIsolatingEventStoredecorator that adjusts sourcing criteria to include the testId tag — so the storage engine only returns matching events at query timeFixtureCustomizerthat wraps the command bus, event sink, and recording components with per-fixture stamping and filteringFor the recording side (assertions), each fixture gets
IsolatingRecordingCommandBusandIsolatingRecordingEventSinkwrappers that filter the shared recording lists by testId. This keeps the recording infrastructure (which is shared at the configuration level) unaware of isolation — the filtering is layered on top.While building this, I also ran into an issue where
and()was callingnew AxonTestFixture(configuration, customization), which re-invoked theFixtureCustomizerand generated a new testId — making events from phase 1 invisible to phase 2. To fix this I introduced aTestContextthat gets created once per fixture and reused across the entire phase chain. This also simplified the phase constructors from 4-7 individual parameters down to a single parameter, which is a nice cleanup on its own.Scope
This addresses event sourcing isolation only. It doesn't solve projection/read-model isolation (where tests share a database for read models) — that's a different problem that would require user-side solutions since the framework doesn't own the projection storage.
Changes
TestIsolationEnhancer— the opt-in enhancerIsolatingCommandBus,IsolatingEventSink— stamp outgoing messages with testIdIsolatingEventStore,IsolatingEventStoreTransaction— adjust sourcing criteria to include testId tagIsolatingRecordingCommandBus,IsolatingRecordingEventSink— filter recorded messages by testIdFixtureCustomizer,FixtureConfiguration— per-fixture extension point and its input/output recordTestContext— per-test context that flows through the phase chainAxonTestFixtureand all phase classes refactored to useTestContextRelated to #4252