DomainRuntimedocs
Environments
GuideWhat works todayReviewed 2026-09-22

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:

RunsChecksSpeed
Unityour execute, with a fake runtimeyour logic: decisions, what gets written, errorsmilliseconds
End to endyour domain in a real environmentschema, permissions, contracts, live queriesseconds

Unit: call execute directly

Every action is reachable from the domain, so a test calls it with the input and a runtime you control:

src/domain.test.ts
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:

e2e/orders.test.ts
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.

On this page