Testing Realms
createTestRealm — a deterministic in-memory world for testing rules, intents, predictors, and rollback UX with no network and no Cloudflare.
@nice-code/realm/testing runs the whole loop — engine, acceptor, any number of clients and replicas — over a zero-network loopback, deterministically. Your rules, intents, predictors, and rollback UX get real tests with no sockets and no Cloudflare:
import { createTestRealm } from "@nice-code/realm/testing";
const world = createTestRealm(marketRealm, { ctx, initialState });const alice = world.asAvatar({ type: "consumer", persistentId: "alice" });
alice.realm.update((draft) => { draft.presence.alice = { cursorX: 1, cursorY: 1 }; });await world.settleAll();
expect(world.engine.state.presence.alice).toBeDefined();expect(alice.realm.store.state).toEqual(world.serverSlice(alice.avatar));The world
Section titled “The world”createTestRealm(realm, options) takes ctx, initialState, and optionally limits, logWindow, and pendingExpiryMs (handy for making expiry tests fast). It returns:
| Member | What it does |
|---|---|
world.engine | The real RealmServerEngine — assert on engine.state directly. |
world.asAvatar({ type, persistentId }, connectOptions?) | A connected avatar handle: { realm, avatar, rejections, … }. The second argument passes per-avatar connect options through (assertOnAttach, coalesceMs, pendingExpiryMs, staleProbeAfterMs) — so presence asserts and durability scenarios test exactly as they ship. |
world.asReplica({ type, persistentId }) | A connected replica handle. |
world.update(fn) | Server-authored mutation (r.serverOnly paths) — sugar for engine.update. |
world.settleAll() | Drain the coalescing microtask + settle the round-trips. |
world.serverSlice(avatar) | The oracle: the server’s current sliced view for an avatar. |
That last one is the assertion that matters most — after any sequence of writes settles, every client’s projection should equal its serverSlice:
expect(alice.realm.store.state).toEqual(world.serverSlice(alice.avatar));If those two ever diverge after settleAll(), something is wrong — in your rules, your predictor, or (tell us!) the library.
Observing the optimistic window
Section titled “Observing the optimistic window”Delivery is normally synchronous-ish (writes flush on a microtask). To see the pending state, park the server’s replies:
alice.holdServerFrames(); // server → client frames queue upconst h = alice.realm.update((d) => { d.presence.alice.cursorX = 5; });expect(h.status).toBe("pending"); // optimistic: applied locally, unconfirmedexpect(alice.realm.pending.count).toBe(1);
alice.pump(); // deliver everything parkedawait h; // read-your-writes: store is authoritative nowexpect(h.status).toBe("confirmed");Rejections and rollback
Section titled “Rejections and rollback”Each test client collects its rejections, so predictor-vs-authority tests are one array assertion:
const mallory = world.asAvatar({ type: "consumer", persistentId: "mallory" });mallory.realm.update((d) => { d.presence.alice.cursorX = 99; }); // not hersawait world.settleAll();
expect(mallory.rejections).toHaveLength(1);expect(mallory.rejections[0].rolledBackPaths).toContain("presence.alice.cursorX");// and the rollback held:expect(mallory.realm.store.state).toEqual(world.serverSlice(mallory.avatar));(With a plain — non-serverChecked — alter rule this rejects locally before any frame is sent; the shape of the test is the same either way.)
Scripting the reconnect matrix
Section titled “Scripting the reconnect matrix”disconnect() / reconnect() drop and restore a client’s link — frames in flight are lost, exactly like the real thing:
alice.disconnect();world.update((d) => { d.config.open = false; }); // happens while she's awayalice.reconnect();await world.settleAll();
// she resumed via patch replay and converged:expect(alice.realm.store.state).toEqual(world.serverSlice(alice.avatar));This is the harness the library’s own suite is built on — reconnect/replay, rule enforcement, intent idempotency, pending-expiry, and rollback behavior are all expressible in a few lines each.