Environments
Testing
Unit-test actions with a fake runtime; test permissions and contracts on a real environment.
Unit tests work today with any test runner. The end-to-end helpers exist in
@domainruntime/testing and run in our CI; they are not yet published to npm.Test at two levels:
| Runs | Checks | Speed | |
|---|---|---|---|
| Unit | your execute, with a fake runtime | your logic: decisions, what gets written, errors | milliseconds |
| End to end | your domain in a real environment | schema, permissions, contracts, live queries | seconds |
Unit: call execute directly
Every action is reachable from the domain, so a test calls it with the input and a runtime you control:
import { describe, expect, it, vi } from "vitest";
import orders from "./domain";
function fakeRuntime(actor: { subject: string } | null) {
const writes: unknown[] = [];
const db = {
tx: new Proxy({}, { get: (_, entity) => new Proxy({}, { get: (_, id) => ({ update: (v) => ({ entity, id, v }) }) }) }),
transact: vi.fn(async (ops) => { writes.push(ops); }),
query: vi.fn(async () => ({ orders_order: [] })),
};
return { writes, runtime: { env: { actor }, db: async () => db } };
}
describe("orders.place", () => {
it("rejects anonymous callers before writing", async () => {
const { runtime, writes } = fakeRuntime(null);
await expect(
orders.actions.place.execute({ input: { orderId: crypto.randomUUID(), total: 10 }, runtime }),
).rejects.toThrow("Sign in");
expect(writes).toHaveLength(0);
});
it("validates its contract", () => {
expect(() => orders.actions.place.input.parse({ orderId: "x", total: -1 })).toThrow();
});
});Calling execute directly skips the runtime: input is not parsed and permissions are not applied. Test the contract with action.input.parse and action.output.parse, and permissions end to end. With the planned verbs the fake runtime becomes { auth, query, tx }.
End to end: a real environment
Permissions, uniqueness and live queries are only faithful on the real runtime. Create a disposable environment, publish your domain, act as a user, and destroy it:
import { createEnvironment, configFromEnv } from "@domainruntime/testing/e2e";
import orders from "../src/domain";
const env = await createEnvironment(configFromEnv());
try {
await env.pushDomain(orders);
// call actions and read as a minted user
} finally {
await env.destroy();
}What to test where
- Decisions — who may, what is rejected, what gets computed: unit.
- Idempotency — a second call with the same unique key updates instead of duplicating: end to end, because it depends on
.unique()in the real database. - Permissions — end to end, as two different users, against a production-mode release (development runs actions as an administrator today).
- Workflows — unit-test each step function; the whole run end to end.