Skip to main content

Testing Event-Sourced Applications

Start here

If you use Dispatch without event sourcing (e.g., as a MediatR replacement), the Testing Dispatch Handlers guide covers unit testing handlers, verifying middleware behavior, and integration testing the pipeline.

Testing event-sourced applications requires different strategies than traditional CRUD applications. Instead of asserting database state, you verify that the correct events are raised and that aggregates behave correctly when replaying historical events.

Before You Start​

  • .NET 10.0
  • Install the testing packages:
    dotnet add package Excalibur.Dispatch.Testing
    dotnet add package Excalibur.Dispatch.Testing.Shouldly # optional
  • Familiarity with handlers and event sourcing concepts

Testing Pyramid for Event Sourcing​

┌─────────────────┐
│ E2E Tests │ ← Full system with real transport
│ (few) │
├─────────────────┤
│ Integration │ ← Real database, real event store
│ (some) │
├─────────────────┤
│ Unit Tests │ ← In-memory, fast, isolated
│ (many) │
└─────────────────┘

What to Test​

LayerWhat to TestHow
AggregatesBusiness rules, state transitions, event generationAggregateTestFixture
Event HandlersProjections update correctly, side effects triggeredMock event store
RepositoriesLoad/save roundtrips, concurrency handlingIntegration tests with real store
SagasState machine transitions, compensating actionsAggregateTestFixture pattern

Excalibur.Testing Package​

Install the testing utilities:

dotnet add package Excalibur.Testing

This package provides:

  • AggregateTestFixture<T> — Fluent Given-When-Then API for aggregate testing
  • Test doubles and in-memory infrastructure — shared fakes for handler and repository tests

The conformance test kits are a separate package — Excalibur.Testing.Conformance. See the conformance toolkit.

Test Framework Compatibility​

Excalibur.Testing is framework-agnostic. Use it with:

  • xUnit (recommended)
  • NUnit
  • MSTest
  • Any other .NET test framework

The fixture throws TestFixtureAssertionException on failures, which all test runners recognize as test failures.

Quick Example​

using Excalibur.Testing;
using Xunit;

public class OrderTests
{
[Fact]
public void Create_order_raises_OrderCreated_event()
{
new AggregateTestFixture<Order>()
.Given() // No prior events - new aggregate
.When(order => order.Create("customer-123"))
.Then()
.ShouldRaise<OrderCreated>(e => e.CustomerId == "customer-123");
}

[Fact]
public void Cannot_ship_cancelled_order()
{
new AggregateTestFixture<Order>()
.Given(
new OrderCreated { OrderId = "123", CustomerId = "C1" },
new OrderCancelled { OrderId = "123", Reason = "Out of stock" })
.When(order => order.Ship())
.ShouldThrow<InvalidOperationException>("cancelled");
}
}

Testing Categories​

Aggregate Testing​

Unit test your aggregates using the Given-When-Then pattern. Fast, isolated, no external dependencies.

Repository Testing​

Integration test your event store interactions. Verify load/save roundtrips and concurrency.

Integration Tests​

End-to-end testing with real infrastructure using TestContainers.

Dispatch Pipeline Testing​

The Excalibur.Dispatch.Testing package provides purpose-built test infrastructure for the Dispatch pipeline. These tools let you test handlers, middleware, and transport interactions without real message brokers.

dotnet add package Excalibur.Dispatch.Testing
dotnet add package Excalibur.Dispatch.Testing.Shouldly # optional, for fluent assertions

Test Harness​

DispatchTestHarness builds a real Dispatch pipeline with DI, automatically tracks all dispatched messages, and provides MessageContextBuilder for creating test contexts with sensible defaults.

Transport Test Doubles​

In-memory implementations of ITransportSender, ITransportReceiver, and ITransportSubscriber that record all interactions for assertions. Test transport flows without RabbitMQ, Kafka, or any real infrastructure.

Shouldly Assertions​

Nine fluent assertion extensions for IDispatchedMessageLog, InMemoryTransportSender, and InMemoryTransportReceiver. Produces domain-specific failure messages.

Testing Dispatch Handlers​

Unit test handlers directly with FakeItEasy, test middleware behavior, and integration test the full pipeline.

Best Practices​

DO​

  • Test one behavior per test method
  • Use descriptive test names that explain the scenario
  • Test both happy paths and error cases
  • Verify events are raised with correct data
  • Test idempotency where applicable

DON'T​

  • Test implementation details (private methods)
  • Skip negative test cases
  • Couple tests to event serialization format
  • Test the framework itself (trust Excalibur's tests)

Next Steps​

  1. Start with Aggregate Testing for unit tests
  2. Add Repository Testing for integration coverage
  3. See Integration Tests for full system tests
  4. Use the Test Harness for pipeline-level testing

See Also​