Testing

The stub transport, custom transports for full-request assertions, and deterministic generators.

Validation, defaults, serialization, the typed method tree: everything a send does before the network runs for real under the stub transport, so tests exercise your schema without any HTTP. The compiler is part of the test surface too: wrong payloads, unknown events, and missing fields fail at build time.

The stub transport

stubTransport is exported from the runtime subpath and plugs into the transport option:

import { createRepostClient } from "@repost/client";
import { stubTransport } from "@repost/client/runtime";
 
const repost = createRepostClient({
  apiKey: "test",
  transport: stubTransport,
});
 
const result = await repost.webhooks.user.created({
  customerId: "customer_test",
  data: { id: "user_test", email: "[email protected]" },
});
 
console.log(result.id); // "msg_stub"

The returned result echoes the envelope's type, customerId, and timestamp with the fixed id msg_stub. Credentials still resolve before the stub runs, so tests exercise the production configuration path; any non-empty key satisfies it.

stubTransport is imported from @repost/client/runtime, not from the package root.

Asserting on the full request

Use the native testing helper to assert on the serialized request and idempotency key. It implements the same one-attempt Transport interface as the production transport:

import { ScriptedStubTransport } from "@repost/client/testing";
 
const transport = new ScriptedStubTransport().enqueueResponse(
  202,
  JSON.stringify({
    id: "msg_test",
    type: "user.created",
    customerId: "customer_test",
    timestamp: "2026-01-01T00:00:00.000Z",
  }),
  { "content-type": "application/json" },
);
 
const repost = createRepostClient({
  apiKey: "test",
  transport,
  generators: { now: () => "2026-01-01T00:00:00.000Z" },
});
 
await repost.webhooks.user.created({
  customerId: "customer_test",
  data: { id: "user_test", email: "[email protected]" },
});
 
expect(JSON.parse(transport.requests[0].bodyText)).toMatchObject({
  type: "user.created",
  customerId: "customer_test",
});
expect(transport.requests[0].idempotencyKey).toBeTruthy();

bodyText, bodyBytes, headers, attempt number, timeout, and idempotency key are captured from the actual runtime attempt.

Deterministic values

Pin the injectable generators to freeze timestamps and generated ids:

const repost = createRepostClient({
  apiKey: "test",
  transport: stubTransport,
  generators: {
    now: () => "2026-01-01T00:00:00.000Z",
    uuid: () => "00000000-0000-4000-8000-000000000000",
  },
});

With now pinned, the envelope timestamp and every @default(now()) field are fixed.

Testing retry behavior

Queue failures and responses on ScriptedStubTransport, then use deterministicClientOptions() or ManualClock from @repost/client/testing to advance retry delays without sleeping. Set maxAttempts, retryBaseDelayMs, and attemptTimeoutMs on the client; retries never live inside the transport.

Continue