Skip to content

Testing

Every handler carries examples, and atlas test runs them all:

Terminal window
npx atlas test --dir src/capabilities
Handler An example gives And expects
Command input, state, optional actor emits, writes, removes, sends, schedules, facts, or rejects
Reaction (.on) fact, state the same effects, or rejects
Projection fact rows (partial match, in order)
View query input, rows, optional actor, state, now items, or rejects
Record query input, state, optional actor, now result, or rejects

Example state is partial: missing reads default to null. An example that sets actor (even null) enforces access; without it, it runs as SYSTEM.

dispatch returns before reactions run. In a test, build a runtime over the memory store and drain() before asserting:

import { test } from 'node:test';
import assert from 'node:assert/strict';
import { createRuntime } from '@ts-atlas/core/runtime';
import { loadSystem } from '@ts-atlas/core/system';
test('an order shows up in MyOrders', async () => {
const system = await loadSystem('src/capabilities');
const runtime = createRuntime({ capabilities: system.capabilities.map((c) => c.capability) });
const ada = { id: 'c1', roles: [] };
await runtime.dispatch({ type: 'PlaceOrder', orderId: 'o1', lines: [{ sku: 'mug', quantity: 1, unitPriceCents: 900 }] }, { actor: ada });
await runtime.drain(); // reactions and projections are delivered after the command commits
const page = await runtime.query('MyOrders', {}, { actor: ada });
assert.equal(page.outcome, 'ok');
});

drain() delivers until every consumer is caught up, bounded by maxSteps handler runs per call. Tests that use drain() do not need await: 'views'.

Inject the clock and move it. drain() releases only scheduled entries already due by the runtime clock:

import { time } from '@ts-atlas/core/runtime';
let at = '2026-01-01T00:00:00.000Z';
const runtime = createRuntime({ capabilities, clock: () => at });
// ... dispatch something that schedules ExpireOrder in one hour
at = time.add(at, 3_600_000);
await runtime.drain();

createRuntime({ beforeCommit }) is a test seam that runs between decide and commit (commands and reactions), so a test can simulate a concurrent writer or a crash. The repository’s test/retry.test.ts and test/batch.test.ts use it.

runtime.trace() returns the newest events (traceLimit, default 1000); observe(event) sees every one. Each event has capability, handler, trigger, outcome (accepted, rejected, failed, warning), code, reason, emitted, sent and attempts.

Run the shared contract suite from a *.test.ts file:

storeContract('my-db', async () => ({ store, close }));

It exercises the commit outcomes (conflict and poison included), depth, leases and holders. Inside the Atlas repository, Postgres tests run with npm run test:pg (serially: the delivery horizon is cluster-wide).