Table of Contents

Chronicle

When Cratis.Arc.Chronicle.Testing (or the Cratis.Testing meta-package) is referenced, CommandScenario<TCommand> is automatically extended with an in-memory event scenario. No separate class or base type is needed.

The extension is wired via ChronicleCommandScenarioExtender, which implements ICommandScenarioExtender and is discovered automatically by CommandScenario<TCommand> at construction time using the Cratis type discovery system.

Package

<PackageReference Include="Cratis.Arc.Chronicle.Testing" />

Or via the meta-package:

<PackageReference Include="Cratis.Testing" />

Basic Usage

Use the same CommandScenario<TCommand> class as for non-Chronicle commands. When the Chronicle testing package is present, three extension properties are available directly on the scenario:

Property Type Purpose
EventScenario EventScenario The full scenario object — use for seeding via Given
EventLog IEventLog The in-memory event log — use for appending and assertions
EventSequence IEventSequence The same instance as EventLog — use with Chronicle's assertion helpers
public class when_registering_author
{
    readonly CommandScenario<RegisterAuthor> _scenario = new();

    [Fact]
    public async Task should_succeed()
    {
        var result = await _scenario.Execute(new RegisterAuthor("Jane Austen"));
        result.ShouldBeSuccessful();
    }

    [Fact]
    public async Task should_have_appended_registered_event()
    {
        await _scenario.Execute(new RegisterAuthor("Jane Austen"));
        await _scenario.EventLog.ShouldHaveAppendedEvent<AuthorRegistered>(EventSequenceNumber.First);
    }
}

Seeding Pre-existing Events with Given

Use _scenario.EventScenario.Given to append events to the in-memory event log before the command runs. Call ForEventSource with the event source identifier, then pass the pre-existing events to Events:

public class when_registering_author_with_same_name
{
    readonly CommandScenario<RegisterAuthor> _scenario = new();

    [Fact]
    public async Task should_not_succeed()
    {
        await _scenario.EventScenario.Given.ForEventSource(AuthorId.New()).Events(new AuthorRegistered("Jane Austen"));
        var result = await _scenario.Execute(new RegisterAuthor("Jane Austen"));
        result.ShouldNotBeSuccessful();
    }

    [Fact]
    public async Task should_not_have_appended_a_second_event()
    {
        await _scenario.EventScenario.Given.ForEventSource(AuthorId.New()).Events(new AuthorRegistered("Jane Austen"));
        await _scenario.Execute(new RegisterAuthor("Jane Austen"));
        await _scenario.EventLog.ShouldHaveTailSequenceNumber(EventSequenceNumber.First);
    }
}

Seed events before calling Execute so they are present when the command handler runs.

EventLog Assertion Helpers

Chronicle provides a set of assertion helpers that extend IEventSequence directly. Call them on _scenario.EventLog or _scenario.EventSequence after Execute. For the full list of available assertions, see Append Assertions.

Multiple Events

When a command appends several events, assert each one by its sequence number:

public class when_completing_order
{
    readonly CommandScenario<CompleteOrder> _scenario = new();

    [Fact]
    public async Task should_succeed()
    {
        await _scenario.EventScenario.Given.ForEventSource(OrderId.New()).Events(new OrderPlaced("item-1", 3));
        var result = await _scenario.Execute(new CompleteOrder());
        result.ShouldBeSuccessful();
    }

    [Fact]
    public async Task should_have_appended_two_events()
    {
        await _scenario.EventScenario.Given.ForEventSource(OrderId.New()).Events(new OrderPlaced("item-1", 3));
        await _scenario.Execute(new CompleteOrder());
        await _scenario.EventLog.ShouldHaveTailSequenceNumber(new EventSequenceNumber(1));
    }

    [Fact]
    public async Task should_have_appended_completed_event()
    {
        await _scenario.EventScenario.Given.ForEventSource(OrderId.New()).Events(new OrderPlaced("item-1", 3));
        await _scenario.Execute(new CompleteOrder());
        await _scenario.EventLog.ShouldHaveAppendedEvent<OrderCompleted>(new EventSequenceNumber(1));
    }
}

Sequence numbering: Sequence numbers are zero-based. EventSequenceNumber.First is 0. The second event is new EventSequenceNumber(1), the third new EventSequenceNumber(2), and so on. ShouldHaveTailSequenceNumber reports the number of the last appended event — so two total events means a tail of 1.

What the Extension Provides

When Cratis.Arc.Chronicle.Testing is referenced, ChronicleCommandScenarioExtender registers the following services automatically:

  • IEventTypes → discovered from the assemblies loaded in the test process (same convention used in production)
  • IEventLog → backed by the real in-process Chronicle kernel (no server required)
  • IEventSequence → the same in-process instance

It also populates the scenario context with an EventScenario instance, which is exposed through three C# 14 extension properties:

  • EventScenario — the full scenario, including the Given builder for seeding events
  • EventLog — shortcut to EventScenario.EventLog for assertions
  • EventSequence — shortcut to EventScenario.EventSequence for assertion helpers