# `Chronicle.Testing`
[🔗](https://github.com/Cratis/Chronicle.Elixir/blob/main/lib/chronicle/testing.ex#L4)

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.

# `appended_events`

```elixir
@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.

# `read_model`

```elixir
@spec read_model(atom(), module(), String.t()) :: {:ok, struct() | nil}
```

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.

# `start_supervised_client`

```elixir
@spec start_supervised_client(keyword()) :: {:ok, atom()} | {:error, term()}
```

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.EventSource` modules to register
  * `:constraints` — standalone constraint modules (each
    `use 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 by
    `Chronicle.Testing.Projections` instead 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.

# `stop_supervised_client`

```elixir
@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.

# `wait_for_observers`

```elixir
@spec wait_for_observers(atom()) :: :ok | {:error, [term()]}
```

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}`).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
