Skip to content

Lift the ProjectionScenario test harness into JasperFx.Events - #616

Merged
jeremydmiller merged 1 commit into
mainfrom
feat/projection-scenario-lift
Aug 3, 2026
Merged

Lift the ProjectionScenario test harness into JasperFx.Events#616
jeremydmiller merged 1 commit into
mainfrom
feat/projection-scenario-lift

Conversation

@jeremydmiller

Copy link
Copy Markdown
Member

Cross-store lift of Marten's EventProjectionScenario test support, following the ergonomics/quality pass in JasperFx/marten#5127 (merged as marten#5132). Fixing the shape first was deliberate — the shared surface starts from the corrected API rather than inheriting the throwaway StreamAction returns and the copy-pasted assertion overloads.

This also closes the intent of JasperFx/polecat#404 without anyone hand-porting 15 overloads: Polecat subclasses the shared type instead of maintaining a parallel implementation.

What's here

New JasperFx.Events.TestSupport namespace in the main library (it ships in the product assembly because users reach it through their store's Advanced surface):

  • ProjectionScenario<TOperations, TQuerySession> — scripts a sequence of event appends (through the shared IEventOperations surface) and document assertions, then executes them in order. Consecutive appends batch into one commit; pending work is saved before each assertion and once more at the end, which is the marten#5126 trailing-append rule. A failed action stops the scenario and reports how many steps were skipped; failed assertions accumulate and report together. Scenarios are single-use — re-execution throws rather than silently doing nothing. When the store has any async projections the scenario stands up an IProjectionDaemon for the duration and waits for non-stale data after each batch, with a configurable Timeout (default 30s) that honors the caller's CancellationToken.
  • Seven abstract seam membersDeleteExistingDataAsync, HasAnyAsyncProjections, BuildDaemonAsync, OpenSession, SaveChangesAsync, EventsFor, LoadDocumentAsync<T>(session, object id, ct). Deliberately the same shape EventStoreComplianceFixture<TOperations, TQuerySession> already asks of each store, including the object id load dispatch — so a store that already has a compliance fixture has already written every one of these.
  • DocumentShouldExist / DocumentShouldNotExist take object ids and dispatch through LoadDocumentAsync. That collapses what were four typed overloads per assertion into one, and typed call sites keep compiling since everything converts to object.
  • StartStream overloads that generate their own stream id return that Guid; the rest of the append surface returns void, since these operations only queue closures and there is no meaningful result until execution.
  • ProjectionScenarioException (aggregate, carries the step-by-step report) and ProjectionScenarioAssertionException (typed assertion failures, distinguishable from infrastructure errors inside the aggregate).

The generic closure mirrors IEventStore<TOperations, TQuerySession> and the compliance fixture — the products close the pair differently and convergence stays a non-goal.

Tests

15 new unit tests in EventTests/TestSupport drive the harness against a fake store closure via NSubstitute, so the sequencing logic is covered with no database: deferred execution and step ordering, flush points (before each assertion, once at the end, including the arrange-only case), typed assertion failures and accumulation, action fail-fast with skip reporting and no final flush, the re-execution guard, daemon start/wait/stop plus the timeout value, tenant id flowing to both session and daemon, and session disposal. EventTests is 672/672 on net9.0.

Consumer status

Marten's adoption branch (feat/5127-scenario-lift) is built and green against a local pack of this branch — Marten.slnx compiles clean and the full scenario suite is 15/15 on net9.0. Marten's ProjectionScenario becomes a ~70-line subclass; seven files and roughly 400 lines of duplicated harness delete. That PR opens once this is merged and published.

🤖 Generated with Claude Code

…ents

Cross-store lift of Marten's EventProjectionScenario test support
(marten#5127), so Marten and Polecat can share one implementation
(closes the intent of polecat#404 without hand-porting overloads).

New JasperFx.Events.TestSupport namespace:

- ProjectionScenario<TOperations, TQuerySession>: scripts a sequence of
  event appends (through the shared IEventOperations surface) and
  document assertions, then executes them in order. Consecutive appends
  batch into one commit; pending work is saved before each assertion and
  once more at the end (the marten#5126 trailing-append rule). A failed
  action stops the scenario; failed assertions accumulate and report
  together in ProjectionScenarioException. Scenarios are single-use --
  re-execution throws. Stands up an IProjectionDaemon for the run when
  the store has any async projections, with a configurable non-stale
  timeout (default 30s). The generic closure mirrors
  IEventStore<TOperations, TQuerySession> and the compliance fixture.
- Seven abstract seam members, deliberately the same shape the
  EventStoreComplianceFixture already uses: DeleteExistingDataAsync,
  HasAnyAsyncProjections, BuildDaemonAsync, OpenSession, SaveChangesAsync,
  EventsFor, and LoadDocumentAsync<T>(session, object id, ct).
- DocumentShouldExist/DocumentShouldNotExist take object ids and dispatch
  through LoadDocumentAsync, so stores need no per-id-type overloads.
- StartStream overloads that generate their own stream id return that
  Guid; the rest of the append surface returns void (the operations only
  queue closures -- there is no meaningful result until execution).
- ProjectionScenarioException (AggregateException with the step-by-step
  report) and ProjectionScenarioAssertionException (typed assertion
  failures, distinguishable from infrastructure errors).

Concrete stores subclass with ~40 lines of one-liners; Marten's adoption
branch is marten feat/5127-scenario-lift.

Tests: 15 new EventTests unit tests drive the harness through a fake
store closure (NSubstitute) covering step ordering and deferred
execution, flush points, arrange-only commit, typed assertion failures,
action fail-fast + skip reporting, re-execution guard, daemon lifecycle
and timeout, tenant flow-through, and session disposal. EventTests
672/672 on net9.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant