Repository navigation
Document unit testing event-sourced handlers with StubEventStream (#5455, #5458) - #5463
Merged
Merged
Conversation
, #5458) Closes #5455 and #5458, which are the same ask from two angles: StubEventStream<T> has lived in src/Marten/Events/ for years and `grep -r StubEventStream docs/` returned nothing. docs/testing/ had exactly one page, integration.md, and the gap was the other half -- unit testing an event-sourced decision without Postgres. Part 1, the docs. New docs/testing/unit-testing-event-handlers.md, beside Integration Testing in the Testing nav, with every sample pulled from src/EventSourcingTests/StubEventStreamTests.cs through mdsnippets so it cannot drift from the API. It covers the single-stream handler, a null aggregate as a stream that does not exist yet, the "nothing to append" outcome, a two-stream command with Id/Key wiring, EventsAppended (raw bodies) vs Events (IEvent envelopes), the settable versions and identities, why this beats mocking IEventStream<T>, and the boundary -- the stub records, it does not persist, project, or ever throw ConcurrencyException. Cross-referenced from command_handler_workflow.md and integration.md. StubEventStreamTests.cs was two assertions long; it is now the snippet source, with the original append_one/append_many kept so Marten's subclass stays covered. Part 2, what happens to Marten's copy. Taking option (1) from #5458: Marten.Events.StubEventStream<T> is now a thin subclass of JasperFx.Events.StubEventStream<T>, so existing tests keep compiling and there is one implementation. As the issue predicted, this widens Key to string? and makes Cancellation settable. The docs name the JasperFx type as the one to reach for. This needs JasperFx 2.72.0 -> 2.73.2, since the store-agnostic type shipped in 2.73.0. See the new note in Directory.Packages.props -- and do not pin 2.73.0, which dropped EventModelClaim's two-argument constructor before 2.73.1 restored it. Found while writing the alias sample: the StoreOptions overload never worked. It built `new EventGraph(options)`, a second graph with its own empty event type cache, so an alias registered through options.Events.MapEventType<T>("...") never reached the stream and Events reported the conventional name anyway -- the one thing that overload exists for. It now uses options.EventGraph, and the new marten_stub_honors_configured_event_aliases test pins it. Verified on PostgreSQL 17: EventSourcingTests 2280/2280, CoreTests 674/674, DaemonTests 323/323, DaemonTests.ManualOnly compiles, full solution builds, and the vitepress docs build is clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01481eiEZg1Bv6pu4DrgPRg3
This was referenced Sep 22, 2026
Closed
This was referenced Oct 1, 2026
Closed
This was referenced Oct 9, 2026
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.
Closes #5455
Closes #5458
The two issues are the same ask from different angles, so they land together.
StubEventStream<T>has lived insrc/Marten/Events/for years andgrep -r StubEventStream docs/returned nothing — its only published exposure was as an aside on a Wolverine page.docs/testing/had exactly one page,integration.md; this is the other half of that section.Part 1 — the docs (the actual ask)
New
docs/testing/unit-testing-event-handlers.md, added to theTestingnav beside Integration Testing. Every sample is pulled fromsrc/EventSourcingTests/StubEventStreamTests.csthrough mdsnippets, so it cannot drift from the API. It covers:IEventStream<T>— construct the stub, call it, assert onEventsAppendednullaggregate as a stream that does not exist yet, which is how a handler chooses between starting and appendingId/Keywiring the command's identities to the streamsEventsAppended(raw bodies) vsEvents(IEventenvelopes)StartingVersion/CurrentVersionfor a version-sensitive handlerSubstitute.For<IEventStream<T>>()+Received(1).AppendOne(...)— that verifies a call, not an outcome, and breaks on a behavior-preserving refactorConcurrencyException. Those belong inintegration.md.Cross-referenced from
command_handler_workflow.md(per #5455) and fromintegration.md.StubEventStreamTests.cswas two assertions long and is now the snippet source, with the originalappend_one/append_manykept so Marten's subclass stays covered.Part 2 — Marten's own copy
Taking option (1) from #5458:
Marten.Events.StubEventStream<T>is now a thin subclass ofJasperFx.Events.StubEventStream<T>. Existing tests keep compiling, there is one implementation rather than two types of the same name behaving slightly differently, and the docs name the JasperFx type as the one to reach for. As the issue predicted, this widensKeytostring?and makesCancellationsettable.The store-agnostic type shipped in 2.73.0, so the docs cannot be written against it without the bump.
Directory.Packages.propsgets a note in the file's usual style. Do not pin 2.73.0 — #859/#863 droppedEventModelClaim's two-argument constructor there and restored it in 2.73.1. Nothing in Marten binds that type today, but the floor is 2.73.1 for anyone who does.Everything else in the span is inert here: an event-model source disagreement naming its source,
ModelCollapsecarrying structured names, and acodegen testfix.A bug found while writing the alias sample
The
StoreOptionsoverload never worked. It builtnew EventGraph(options)— a second graph with its own empty event type cache — so an alias registered throughoptions.Events.MapEventType<T>("...")never reached the stream, andEventsreported the conventional name anyway. That is the one thing the overload exists for; its doc comment says "you only care about this overload if you are customizing event type aliases".It now uses
options.EventGraph, the graph the aliases were actually configured on, andmarten_stub_honors_configured_event_aliasespins it. (This test failed against the old behavior, which is how it surfaced.)Verification
All on PostgreSQL 17:
EventSourcingTests2280/2280CoreTests674/674DaemonTests323/323DaemonTests.ManualOnlycompiles (no CI job builds it)src/Marten.slnxbuild cleanvitepress build docsclean, all 7 snippets resolvedmdsnippets rewrites ~30 doc files with pre-existing drift on master; those were reverted, so the only doc changes here are this page, the two cross-reference tips, and the nav entry.
Not in scope
The Wolverine-side cross-reference is JasperFx/wolverine#4470, and the sibling Polecat and Fisher doc pages are tracked in their own repos.
🤖 Generated with Claude Code
https://claude.ai/code/session_01481eiEZg1Bv6pu4DrgPRg3