Skip to content

Append Assertions

Every Append and AppendMany call returns an IAppendResult. The Cratis.Chronicle.Testing package provides built-in Should* extension methods for asserting on these results.

MethodAsserts
result.ShouldBeSuccessful()Operation succeeded with no violations or errors
result.ShouldBeFailed()Operation failed (any violation or error)
result.ShouldHaveConstraintViolations()At least one constraint violation is present
result.ShouldNotHaveConstraintViolations()No constraint violations are present
result.ShouldHaveConstraintViolationFor(name)A violation for the named constraint is present
result.ShouldHaveConstraintViolation(name)Singular alias for ShouldHaveConstraintViolationFor(name)
result.ShouldHaveConcurrencyViolations()At least one concurrency violation is present
result.ShouldNotHaveConcurrencyViolations()No concurrency violations are present
result.ShouldHaveErrors()At least one error is present
result.ShouldNotHaveErrors()No errors are present

All Should* methods throw AppendResultAssertionException on failure. The exception message describes all violations and errors found, making it easy to diagnose failures.

using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
using Cratis.Chronicle.Testing.EventSequences;
[EventType]
public record TestingAssertionsAuthorRegistered([property: Unique(name: "TestingAssertionsUniqueAuthorName")] string Name);
public static class TestingAssertionsHappyPath
{
public static async Task Run()
{
var scenario = new EventScenario();
var authorId = EventSourceId.New();
var result = await scenario.EventLog.Append(authorId, new TestingAssertionsAuthorRegistered("Jane Smith"));
result.ShouldBeSuccessful();
}
}
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Testing.EventSequences;
public static class TestingAssertionsFailure
{
public static async Task Run()
{
var scenario = new EventScenario();
var authorId = EventSourceId.New();
var newAuthorId = EventSourceId.New();
await scenario.Given
.ForEventSource(authorId)
.Events(new TestingAssertionsAuthorRegistered("Jane Smith"));
var result = await scenario.EventLog.Append(newAuthorId, new TestingAssertionsAuthorRegistered("Jane Smith"));
result.ShouldBeFailed();
}
}

Assert that a specific named constraint fired:

using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsConstraintViolation
{
public static void Assert(AppendResult result)
{
result.ShouldHaveConstraintViolations();
result.ShouldHaveConstraintViolationFor("TestingAssertionsUniqueAuthorName");
}
}

Assert that no constraint violations occurred:

using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsNoConstraintViolation
{
public static void Assert(AppendResult result) => result.ShouldNotHaveConstraintViolations();
}
using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsConcurrency
{
public static void AssertHasViolations(AppendResult result) => result.ShouldHaveConcurrencyViolations();
public static void AssertNoViolations(AppendResult result) => result.ShouldNotHaveConcurrencyViolations();
}
using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsErrors
{
public static void AssertHasErrors(AppendResult result) => result.ShouldHaveErrors();
public static void AssertNoErrors(AppendResult result) => result.ShouldNotHaveErrors();
}

For asserting on the event sequence itself (tail sequence number, event types, and event content), see Event Sequence Assertions.

Example: testing a constraint violation end-to-end

Section titled “Example: testing a constraint violation end-to-end”

Pre-seed state with Given, act with When, then assert on the captured scenario.AppendResult — including constraints declared on ConceptAs<T> properties:

using Cratis.Concepts;
using Cratis.Chronicle.Events;
using Cratis.Chronicle.Events.Constraints;
using Cratis.Chronicle.EventSequences;
using Cratis.Chronicle.Testing.EventSequences;
using Cratis.Specifications;
using Xunit;
// Email is a ConceptAs<string> — uniqueness on a domain value works in-process,
// so the constraint no longer needs to be hidden behind #if !DEBUG.
public record TestingAssertionsEmail(string Value) : ConceptAs<string>(Value);
[EventType]
public record TestingAssertionsAuthorRegisteredWithEmail([property: Unique("TestingAssertionsUniqueAuthorEmail")] TestingAssertionsEmail Email);
public class when_registering_an_author_with_a_taken_email : Specification, IDisposable
{
readonly EventScenario _scenario = new();
AppendResult _result = default!;
Task Establish() =>
_scenario.Given
.ForEventSource(EventSourceId.New())
.Events(new TestingAssertionsAuthorRegisteredWithEmail(new("john@doe.com")));
async Task Because() =>
_result = await _scenario.When
.ForEventSource(EventSourceId.New())
.Events(new TestingAssertionsAuthorRegisteredWithEmail(new("john@doe.com")));
[Fact] void should_be_rejected() =>
_result.ShouldBeFailed();
[Fact] void should_report_the_constraint() =>
_result.ShouldHaveConstraintViolation("TestingAssertionsUniqueAuthorEmail");
public void Dispose() => _scenario.Dispose();
}