Test client
`createTestClient({ seed })` spins up an in-process Hono mock and a typed SDK client wired to it via `fetch` injection: no port, no MSW, no `globalThis` mutation. Every call gets its own session token, so tests in the same module run in parallel safely.
Quick e2e
import { createTestClient, fixtures, assertCart } from "@cimplify/sdk/testing";
const h = createTestClient({ seed: "retail" });
try {
await fixtures.addFirstProduct(h.client);
const cart = await h.client.cart.get();
if (!cart.ok) throw cart.error;
assertCart(cart.value);
console.log(cart.value.items.length, "items");
} finally {
h.dispose();
}The handle
interface TestClientHandle {
client: CimplifyClient; // typed SDK client routed to the in-process mock
mock: AppHandle; // direct mock access: registry, deps, request shortcut
reset: () => void; // wipe in-memory state (cart, orders, sessions)
dispose: () => void; // drop everything; safe to call multiple times
}Options
| Option | Type | Default | Purpose |
|---|---|---|---|
seed | SeedName | "default" | Which industry seed to load |
authMode | "permissive" | "strict" | "permissive" | Strict mode requires real OTP flow |
defaultOtp | string | "123456" | OTP code echoed back by the mock |
frozenAt | Date | live clock | Freeze time for deterministic snapshots |
rngSeed | number | non-deterministic | Seed the mock's RNG for stable IDs |
Parallel-safe
Vitest runs files in parallel and tests within a file in parallel by default. The harness uses the SDK's first-class fetch injection; there is no shared globalThis.fetch patch, no port, no shared state. Each createTestClient call is its own world.
Fixtures
Pre-baked helpers for common setup so tests stay focused on the assertion they care about.
import { fixtures } from "@cimplify/sdk/testing";
// Add the first product (or one by slug); auto-picks first variant.
const { product, variantId } = await fixtures.addFirstProduct(h.client);
// Minimal valid customer object for checkout.
const customer = fixtures.customer({ email: "akua@example.com" });
// Run the full guest auth flow (request OTP → verify) and get a session.
const { token, customerId } = await fixtures.authenticate(h.client, "akua@example.com");Inside vitest
Use beforeEach / afterEach to scope a fresh handle per test. The suites in @cimplify/sdk/testing/suite already do this; reach for createTestClient directly when you need to compose your own scenario.
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { createTestClient, fixtures } from "@cimplify/sdk/testing";
import type { TestClientHandle } from "@cimplify/sdk/testing";
describe("custom flow", () => {
let h: TestClientHandle;
beforeEach(() => { h = createTestClient({ seed: "retail" }); });
afterEach(() => h.dispose());
it("applies a coupon and updates the cart pricing", async () => {
await fixtures.addFirstProduct(h.client);
const r = await h.client.cart.applyCoupon("WELCOME10");
expect(r.ok).toBe(true);
if (!r.ok) return;
expect(parseFloat(String(r.value.pricing.discount))).toBeGreaterThan(0);
});
});Reaching into the mock
The handle's mock property exposes the underlying app for advanced needs: emit a custom event on the bus, peek at registered routes, or run a low-level request().
// Emit a custom event for a webhook receiver under test:
h.mock.deps.bus.emit({
id: "evt_test_1",
type: "order.created",
data: { order_id: "ord_x" },
});
// Make a raw request the SDK doesn't model:
const res = await h.mock.request("/v1/internal/diagnostics", { method: "GET" });Next
MCP server
Cimplify exposes a Model Context Protocol server at api.cimplify.io/mcp. Agents (Claude Code, Cursor, ChatGPT Connectors, Continue, Goose) speak streamable HTTP per the MCP 2025-11-25 spec and drive every CLI workflow as typed tool calls: no shell-out, no help-text parsing, no stdio bridge required.
Suites
Three pre-baked vitest suites cover the common shape contracts. Templates wire them in three lines each: when a new case lands in the SDK, every storefront inherits it on `bun update @cimplify/sdk`.