Own typed in-process worlds or one runtime shared safely across isolated test workers.
Use a stateful emulator only when the assertion needs the provider boundary. Choose the right test boundary separates those tests from unit tests and fakes.
This complete test owns the runtime, one seeded instance, one control-plane arrangement, and one provider-shaped application request:
import { createTestRuntime } from "localhost2137/testing";
import { expect, it } from "vitest";
import config from "../localhost.config.js";
it("arranges a user with an operation and reads it through Slack-shaped HTTP", async () => {
const runtime = await createTestRuntime({
config,
port: 0,
storage: "temporary",
});
try {
const instance = await runtime.createInstance({ seed: true });
try {
const grace = await instance.slack.createUser({ name: "Grace" });
const response = await fetch(new URL("users.list", instance.slack.connection.apiUrl), {
headers: {
authorization: `Bearer ${instance.slack.connection.botToken}`,
},
});
expect(response.status).toBe(200);
await expect(response.json()).resolves.toMatchObject({
members: [
{ id: "U000000", name: "localhost2137-bot" },
{ id: grace.id, name: "Grace" },
{ id: "U_ADA", name: "Ada" },
],
ok: true,
});
} finally {
await instance.destroy();
}
} finally {
await runtime.close();
}
});pnpm exec vitest run test/owned-runtime.test.tsThe nested finally blocks are the ownership model. After createTestRuntime resolves, that scope
must call runtime.close(). After createInstance resolves, its narrower scope must destroy the
instance first. The exact runtime options select loopback, an OS-assigned port, and owned temporary
storage. It does not discover or attach to localhost dev.
The typed handle exposes configured operations, connection values, env, seed(),
reset({ seed }), idle(), clock control, and destroy(). Operations arrange and inspect; the HTTP
request above proves the application-facing path. Do not replace the behavior under test with its
control operation or read plugin databases directly.
runtime.connection contains the loopback control URL and private bearer token for crossing a process boundary.
runtime.control is the corresponding untyped client. Neither is application
configuration. A failed temporary-directory removal is reported as TestRuntimeCleanupError with
retainedStoragePath; treat it as a cleanup failure and retained diagnostic evidence.
This checked example arranges a product and price through typed operations, creates the customer and subscription through the official SDK, advances the instance clock, and reads invoices through the SDK again:
import { createTestRuntime } from "localhost2137/testing";
import { afterEach, describe, expect, it } from "vitest";
import config from "../localhost.config.js";
import { createLocalStripe } from "../src/local-stripe.js";
const runtimes: Array<Awaited<ReturnType<typeof createTestRuntime>>> = [];
afterEach(async () => {
await Promise.all(runtimes.splice(0).map((runtime) => runtime.close()));
});
describe("official Stripe SDK", () => {
it("creates and renews a subscription through normal SDK calls", async () => {
const runtime = await createTestRuntime({ config, port: 0, storage: "temporary" });
runtimes.push(runtime);
const instance = await runtime.createInstance();
try {
const product = await instance.stripe.createProduct({ name: "Pro" });
const price = await instance.stripe.createPrice({
productId: product.id,
unitAmount: 2_500,
});
const client = createLocalStripe(instance.stripe.connection);
const customer = await client.customers.create({
email: "ada@example.test",
name: "Ada",
});
await expect(client.customers.retrieve("cus_missing")).rejects.toMatchObject({
code: "customer_missing",
statusCode: 404,
type: "StripeInvalidRequestError",
});
const subscription = await client.subscriptions.create({
customer: customer.id,
items: [{ price: price.id }],
});
expect(subscription).toMatchObject({
customer: customer.id,
id: "sub_000001",
latest_invoice: "in_000001",
status: "active",
});
await instance.clock.advance("30d");
const invoices = await client.invoices.list({
limit: 10,
subscription: subscription.id,
});
expect(invoices.data.map(({ id }) => id)).toEqual(["in_000001", "in_000002"]);
expect(invoices.data[1]).toMatchObject({
amount_paid: 2_500,
customer: customer.id,
paid: true,
subscription: subscription.id,
});
} finally {
await instance.destroy();
}
});
});pnpm --filter @localhost2137/example-stripe-sdk testClock advancement is a stronger transition than an operation call: it returns after all configured
services reconcile the committed time window and their tracked work. For asynchronous work without
a clock change, await instance.idle() waits for tracked tasks, including nested tracked tasks. It
cannot discover arbitrary fire-and-forget promises. Do not add sleeps around either transition; see
Virtual time and asynchronous work.
A file may create one runtime in beforeAll and close it in afterAll. Each test should still call
createInstance() and destroy that instance in finally. Separate worlds isolate state, seed
status, clock, logs, and storage on the runtime's one server port.
Use reset() only when replacing the same world is part of one scenario. It produces an empty world;
reset({ seed: true }) produces a freshly seeded replacement. Seed is also explicit on
createInstance({ seed: true }) and instance.seed(). Await lifecycle calls rather than overlapping
seed, reset, clock advancement, and destruction.
Vitest global setup owns the runtime. Workers receive only a frozen connection and each selects a unique instance ID. The provided context makes the process boundary explicit:
export interface WorkerRuntimeHarness {
readonly barrier: Readonly<{
readonly directory: string;
readonly participants: number;
}>;
readonly connection: Readonly<{
readonly token: string;
readonly url: string;
}>;
}
declare module "vitest" {
export interface ProvidedContext {
readonly localhost2137: WorkerRuntimeHarness;
}
}Global setup closes partially created resources as well as successful setup:
import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { createTestRuntime } from "localhost2137/testing";
import type { TestProject } from "vitest/node";
import { config } from "../src/config.js";
import type { WorkerRuntimeHarness } from "./runtime-connection.js";
export default async function setup(project: TestProject): Promise<() => Promise<void>> {
const barrierDirectory = await mkdtemp(join(tmpdir(), "localhost2137-vitest-barrier-"));
let runtime: Awaited<ReturnType<typeof createTestRuntime>> | undefined;
try {
const ownedRuntime = await createTestRuntime({ config, port: 0, storage: "temporary" });
runtime = ownedRuntime;
const harness: WorkerRuntimeHarness = Object.freeze({
barrier: Object.freeze({ directory: barrierDirectory, participants: 4 }),
connection: ownedRuntime.connection,
});
project.provide("localhost2137", harness);
return () => closeOwnedResources(ownedRuntime, barrierDirectory);
} catch (cause) {
const failures = await cleanupFailures(runtime, barrierDirectory);
if (failures.length > 0) {
throw new AggregateError([cause, ...failures], "Parallel example setup and cleanup failed.");
}
throw cause;
}
}
async function closeOwnedResources(
runtime: Awaited<ReturnType<typeof createTestRuntime>>,
barrierDirectory: string,
): Promise<void> {
const failures = await cleanupFailures(runtime, barrierDirectory);
if (failures.length > 0) throw new AggregateError(failures, "Parallel example cleanup failed.");
}
async function cleanupFailures(
runtime: Awaited<ReturnType<typeof createTestRuntime>> | undefined,
barrierDirectory: string,
): Promise<unknown[]> {
const failures: unknown[] = [];
await runtime?.close().catch((cause: unknown) => failures.push(cause));
await rm(barrierDirectory, { force: true, recursive: true }).catch((cause: unknown) =>
failures.push(cause),
);
return failures;
}The worker helper owns only a caller-selected ID. Its create failure handling distinguishes a known server rejection from an uncertain response:
import { ControlApiError, type RuntimeClient } from "localhost2137/client";
type InstanceOwnerClient = Pick<RuntimeClient, "createInstance" | "destroyInstance">;
type Outcome<Value> =
| Readonly<{ ok: true; value: Value }>
| Readonly<{ cause: unknown; ok: false }>;
/** Own one known worker instance ID without deleting an authoritative conflict. */
export async function withOwnedInstance<Value>(
runtime: InstanceOwnerClient,
instanceId: string,
use: () => Promise<Value>,
): Promise<Value> {
try {
await runtime.createInstance({ id: instanceId, persistence: "ephemeral" });
} catch (cause) {
if (cause instanceof ControlApiError) throw cause;
return finishOwnership(runtime, instanceId, { cause, ok: false });
}
let primary: Outcome<Value>;
try {
primary = { ok: true, value: await use() };
} catch (cause) {
primary = { cause, ok: false };
}
return finishOwnership(runtime, instanceId, primary);
}
async function finishOwnership<Value>(
runtime: InstanceOwnerClient,
instanceId: string,
primary: Outcome<Value>,
): Promise<Value> {
const cleanup = await destroyIfPresent(runtime, instanceId);
if (!primary.ok) {
if (!cleanup.ok) {
throw new AggregateError(
[primary.cause, cleanup.cause],
`Worker instance ${JSON.stringify(instanceId)} failed and cleanup also failed.`,
{ cause: primary.cause },
);
}
throw primary.cause;
}
if (!cleanup.ok) throw cleanup.cause;
return primary.value;
}
async function destroyIfPresent(
runtime: InstanceOwnerClient,
instanceId: string,
): Promise<Outcome<void>> {
try {
await runtime.destroyInstance(instanceId);
return { ok: true, value: undefined };
} catch (cause) {
if (cause instanceof ControlApiError && cause.code === "INSTANCE_NOT_FOUND") {
return { ok: true, value: undefined };
}
return { cause, ok: false };
}
}Each worker connects, creates one unique ID, asserts isolated state, waits for tracked work, and lets the helper clean up:
import { randomUUID } from "node:crypto";
import { connectRuntime } from "localhost2137/client";
import { describe, expect, inject, it } from "vitest";
import { arriveAtBarrier } from "./barrier.js";
import { withOwnedInstance } from "./owned-instance.js";
import "./runtime-connection.js";
export function defineWorkerContract(label: string, increment: number): void {
describe(label, () => {
const harness = inject("localhost2137");
const runtime = connectRuntime(harness.connection);
const instanceId = `parallel-${randomUUID()}`;
it("owns isolated state on the shared runtime", async () => {
await withOwnedInstance(runtime, instanceId, async () => {
await expect(runtime.executeOperation(instanceId, "counter", "read", {})).resolves.toEqual({
value: 0,
});
await expect(
runtime.executeOperation(instanceId, "counter", "increment", { by: increment }),
).resolves.toEqual({ value: increment });
await arriveAtBarrier(
harness.barrier.directory,
label.split(" ")[0] ?? label,
harness.barrier.participants,
);
await expect(runtime.executeOperation(instanceId, "counter", "read", {})).resolves.toEqual({
value: increment,
});
});
});
});
}The counter operations finish synchronously, so this worker has no tracked work to await. The
callback-driven test in examples/slack-ping-bot/test/ping-pong.test.ts calls instance.idle()
before asserting delivery; What idle() establishes defines
that boundary.
A create ControlApiError is an authoritative server rejection. The helper rethrows it without running the scenario or destroying that ID;
an INSTANCE_CONFLICT can name a world the worker does not own. A transport or protocol failure leaves the outcome uncertain: creation may have committed
before the response was lost, so the helper reconciles only the ID it selected.
After a successful or uncertain create, cleanup ignores only a ControlApiError with
INSTANCE_NOT_FOUND. An AggregateError keeps a primary scenario failure first and as cause when
cleanup also fails. The remote client is intentionally untyped, so operation names and inputs must
come from the mounted plugin's inventory.
Keep the control token in the framework's private in-memory context. Do not log it, inject it into the application, or persist it in an artifact. Instances isolate state and time, not shared plugin config or one callback destination; Callbacks and webhooks covers that parallel boundary.
Run the checked four-worker example:
pnpm --filter @localhost2137/example-testing-parallel testUse connectRuntime against a separately owned localhost dev daemon only for intentional local
scripts. Such a script owns its unique ephemeral instance, never the daemon or someone else's
persistent dev world. CLI workflow and reference documents that access.