Skip to content

Test DOs in-process

White-box Durable Object tests with composeDurableInstance and fake state helpers.

AI generated, pending review Updated 10 days ago · Flare 0.3

Flare ships in-process helpers to exercise Durable Object routes without deploying to workerd. Import them from @flare-ts/core/cloudflare (the module needs a Workers environment: it imports cloudflare:workers at module scope). composeDurableInstance takes the built host.

import {
composeDurableInstance,
DurableState,
FlareDurableObject,
makeEnv,
makeFakeDurableState,
} from "@flare-ts/core/cloudflare";
import { FlareHost, FlareResponse, FlareService } from "@flare-ts/core";
import { cf } from "@flare-ts/core/cloudflare";
class Counter extends FlareService {
public static override deps = [];
#n = 0;
bump() {
this.#n += 1;
}
}
class Room extends FlareDurableObject {
public static override deps = [DurableState, Counter] as const;
}
const host = new FlareHost(cf);
host.scoped(Counter);
const room = host.durableObject(Room);
room.http.get(
"/ping",
{ inject: { ds: DurableState } },
(_ctx, scope) => new FlareResponse(200, { id: scope.ds.id.toString() }),
);
host.http.get("/_", () => new FlareResponse(200)); // Worker front door needs at least one route
host.build();
const inst = composeDurableInstance(
host,
makeFakeDurableState({ name: "room-1" }),
makeEnv({ REGION: "enam" }),
Room,
);
const res = await inst.fetch(new Request("https://do/ping"));
expect(res.status).toBe(200);

composeDurableInstance bypasses the real DO constructor (workerd owns that) and composes the per-instance container and DurableHandler you can drive with inst.fetch().

// The first argument is the DO class's `static deps` allow-list; Counter is
// listed in Room's `static deps` above.
const counter = inst.inject(Room.deps, Counter);
counter.bump();

Use this to unit-test services wired through the DO’s scoped container.

makeFakeDurableState accepts options including in-memory storage from makeFakeStorage():

const storage = makeFakeStorage();
const state = makeFakeDurableState({ name: "room-1", storage });

makeFakeStorage() covers only the KV subset (get, put, delete, list). No SQL: DOs that use state.storage.sql need a real cloudflare:test binding.

Drive WebSocket routes through inst.fetch() with an upgrade Request under the cloudflare:test pool: the upgrade allocates a WebSocketPair, a Workers-runtime global. res.webSocket on the 101 response is the client half of the pair. Use a real cloudflare:test binding when you need the real constructor, alarm, or RPC methods.