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.
Assertion reference
Section titled “Assertion reference”| Method | Asserts |
|---|---|
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.
Happy path assertion
Section titled “Happy path assertion”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(); }}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.testing.EventScenarioimport kotlinx.coroutines.runBlockingimport org.junit.jupiter.api.Assertions.assertTrueimport org.junit.jupiter.api.Test
@EventTypedata class TestingAssertionsAuthorRegistered(val name: String)
class TestingAssertionsHappyPathTests {
@Test fun `appending an event succeeds`() = runBlocking { val scenario = EventScenario() val result = scenario.eventLog.append("author-1", TestingAssertionsAuthorRegistered("Jane Smith"))
assertTrue(result.isSuccess) }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.eventSequences.AppendResult;import io.cratis.chronicle.java.BlockingEventSequence;import io.cratis.chronicle.testing.EventScenario;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
class TestingEventsAssertionsHappyPath {
@EventType record AuthorRegistered(String name) { }
@Test void appendingAnEventSucceeds() { var scenario = new EventScenario("testing", "default"); var eventLog = new BlockingEventSequence(scenario.getEventSequence());
AppendResult result = eventLog.append("author-1", new AuthorRegistered("Jane Smith"));
assertTrue(result.isSuccess()); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Failure assertions
Section titled “Failure assertions”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(); }}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Constraint violation assertions
Section titled “Constraint violation assertions”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"); }}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Assert that no constraint violations occurred:
using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsNoConstraintViolation{ public static void Assert(AppendResult result) => result.ShouldNotHaveConstraintViolations();}import io.cratis.chronicle.events.EventTypeimport io.cratis.chronicle.testing.EventScenarioimport kotlinx.coroutines.runBlockingimport org.junit.jupiter.api.Assertions.assertTrueimport org.junit.jupiter.api.Test
@EventTypedata class TestingAssertionsNoViolationAuthorRegistered(val name: String)
/** * [EventScenario] runs in-process with no kernel behind it, so nothing enforces constraints and * every append comes back free of constraint violations - there is no kernel there to reject one. */class TestingAssertionsNoViolationTests {
@Test fun `an in-process append never carries a constraint violation`() = runBlocking { val scenario = EventScenario() val result = scenario.eventLog.append("author-1", TestingAssertionsNoViolationAuthorRegistered("Jane Smith"))
assertTrue(result.constraintViolations.isEmpty()) }}import io.cratis.chronicle.events.EventType;import io.cratis.chronicle.eventSequences.AppendResult;import io.cratis.chronicle.java.BlockingEventSequence;import io.cratis.chronicle.testing.EventScenario;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
/** * EventScenario runs in-process with no kernel behind it, so nothing enforces constraints and every * append comes back free of constraint violations - there is no kernel there to reject one. */class TestingEventsAssertionsNoConstraintViolation {
@EventType record AuthorRegistered(String name) { }
@Test void anInProcessAppendNeverCarriesAConstraintViolation() { var scenario = new EventScenario("testing", "default"); var eventLog = new BlockingEventSequence(scenario.getEventSequence());
AppendResult result = eventLog.append("author-1", new AuthorRegistered("Jane Smith"));
assertTrue(result.getConstraintViolations().isEmpty()); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Concurrency violation assertions
Section titled “Concurrency violation assertions”using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsConcurrency{ public static void AssertHasViolations(AppendResult result) => result.ShouldHaveConcurrencyViolations();
public static void AssertNoViolations(AppendResult result) => result.ShouldNotHaveConcurrencyViolations();}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Error assertions
Section titled “Error assertions”using Cratis.Chronicle.EventSequences;
public static class TestingAssertionsErrors{ public static void AssertHasErrors(AppendResult result) => result.ShouldHaveErrors();
public static void AssertNoErrors(AppendResult result) => result.ShouldNotHaveErrors();}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.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();}Kotlin does not support this workflow yet.Java does not support this workflow yet.Elixir does not support this workflow yet.TypeScript does not support this workflow yet.