Testing support for applications built on Chronicle: an in-memory fake kernel, deterministic helpers, and assertions — so a spec can exercise commands, reactors, reducers, constraints and concurrency without a running kernel.
Usage
{:ok, client} =
Chronicle.Testing.start_supervised_client(
event_types: [MyApp.Events.AccountOpened],
reducers: [MyApp.Reducers.AccountReducer]
)
:ok = Chronicle.EventSequences.EventLog.append("account-1", %AccountOpened{}, client: client)
:ok = Chronicle.Testing.wait_for_observers(client)
{:ok, %MyApp.ReadModels.Account{} = account} =
Chronicle.Testing.read_model(client, MyApp.Reducers.AccountReducer, "account-1")
Chronicle.Testing.stop_supervised_client(client)Prefer Chronicle.Testing.Case in ExUnit tests, which does this setup/teardown
for you and is safe under async: true.
What runs for real, and what is modeled
start_supervised_client/1 starts a real Chronicle.Client pointed at a
Chronicle.Testing.InMemoryKernel instead of a socket — so command validation,
Chronicle.EventSequences.EventLog's append/read/unit-of-work code,
constraint declarations, and concurrency scopes all run completely unchanged.
Reactors and reducers do not run through the real gRPC observation stream
(the in-memory kernel does not simulate bidirectional streaming): they run
synchronously, in a separate Chronicle.Testing.Dispatcher process, right after
each committed append, by calling the exact same Chronicle.Reactors.Dispatch
and Chronicle.Reducers.Dispatch modules the real stream-handling processes
use.
Reducer read model state is kept by the dispatcher, keyed by event source id —
read_model/3 reads it directly; there is no in-memory ReadModels/Projections
query surface. Projection read models (declared with from/join/removed_with,
not a reducer) are evaluated by Chronicle.Testing.Projections for the supported
subset.
closes_stream constraints and any RPC outside the list in
Chronicle.Testing.InMemoryKernel's moduledoc return
{:error, {:not_supported_in_memory, rpc}} — reach for
Chronicle.Testing.Integration and a real kernel for those.
Summary
Functions
Returns every event appended for event_source_id, decoded into
Chronicle.Events.AppendedEvent structs, in append order.
Reads the read model state a reducer has built for key (typically an event
source id). Call wait_for_observers/1 first if the append that produced it
might not have been processed yet.
Starts a Chronicle.Client supervision tree against an in-memory kernel, and a
dispatcher that runs the given reactors and reducers synchronously after every
committed append.
Stops a client started by start_supervised_client/1: the dispatcher, the
Chronicle.Client supervision tree, and the in-memory kernel, in that order.
Waits for every reactor and reducer the client's dispatcher owns to finish processing every append committed so far. Deterministic — no sleeps: it sends a synchronous call to the dispatcher, which can only answer once its mailbox (where the kernel's append notifications arrive, in order) has drained up to this point.
Functions
@spec appended_events(atom(), String.t()) :: [Chronicle.Events.AppendedEvent.t()]
Returns every event appended for event_source_id, decoded into
Chronicle.Events.AppendedEvent structs, in append order.
Reads the read model state a reducer has built for key (typically an event
source id). Call wait_for_observers/1 first if the append that produced it
might not have been processed yet.
Starts a Chronicle.Client supervision tree against an in-memory kernel, and a
dispatcher that runs the given reactors and reducers synchronously after every
committed append.
Options
:name— the client name other calls address it by (default: a unique generated atom):event_store/:namespace— default to a unique generated name /"Default":event_types— event type modules to register:event_sources—Chronicle.EventSources.EventSourcemodules to register:constraints— standalone constraint modules (eachuse Chronicle.Events.Constraint); combined with constraints declared on:event_types:reactors— reactor modules run synchronously by the dispatcher:reducers— reducer modules run synchronously by the dispatcher:projections— read model modules evaluated byChronicle.Testing.Projectionsinstead of a reducer (optional; this testing kernel does not register or run the server-side projection engine)
Returns {:ok, client}, where client is the name to pass as :client to
Chronicle.EventSequences.EventLog, Chronicle.append/3, etc.
@spec stop_supervised_client(atom()) :: :ok
Stops a client started by start_supervised_client/1: the dispatcher, the
Chronicle.Client supervision tree, and the in-memory kernel, in that order.
Waits for every reactor and reducer the client's dispatcher owns to finish processing every append committed so far. Deterministic — no sleeps: it sends a synchronous call to the dispatcher, which can only answer once its mailbox (where the kernel's append notifications arrive, in order) has drained up to this point.
Returns :ok, or {:error, failures} when a reactor or reducer raised while
processing an event (each failure is {:reactor | :reducer, module, reason}).