cimplify
Testing harness

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

OptionTypeDefaultPurpose
seedSeedName"default"Which industry seed to load
authMode"permissive" | "strict""permissive"Strict mode requires real OTP flow
defaultOtpstring"123456"OTP code echoed back by the mock
frozenAtDatelive clockFreeze time for deterministic snapshots
rngSeednumbernon-deterministicSeed 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

  • Suites Brand, cart-flow, contract; three lines each

  • MSW Same mock for component tests

On this page