Skip to content

TestingMock Utilities

Mock Utilities

Mock clients, record factories, and recording a real API into fixtures, from @noverax/courier-mock.

2 min readboth modes

Mock client and factories

@noverax/courier-mock/testing builds a typed client that answers from mocks and records every call, and defineFactory builds the records it answers with:

ts
import { courier, defineApi, defineGroup } from "@noverax/courier/define"
import { defineFactory } from "@noverax/courier-mock"
import { createMockClient } from "@noverax/courier-mock/testing"

type User = { id: number; name: string; email: string }

const { endpoint } = courier()
const users = defineGroup({ prefix: "users", endpoints: { getAll: endpoint.get("").response<User[]>() } })
const api = defineApi({ baseURL: "https://api.example.com", groups: { users } })

const userFactory = defineFactory<User>({ defaults: { id: 1, name: "Alice", email: "alice@test.com" } })

const { client, mock, history } = createMockClient<typeof api>()
mock("users", "getAll").resolves(
  userFactory.buildSequence(3, (index, base) => ({ ...base, id: index + 1, name: `User ${index + 1}` })),
)

const all = await client.users.getAll() // three users
history.length // 1

createMockClient({ network }) answers as a slow or failing network would: it fails a share of calls and delays the rest, with each route's own override applied, and refuses an upload over its size limit or of a type it does not take, with the status a mock server answers it with. See Network conditions for every field, and how each fault fails a call.

Recording a real API

createRecorder calls a real backend from the API's definition and records each response as a fixture — a failed one too. Its client answers as the source's own client does: the data, or an HTTPError with the status and body.

ts
import { createMockClient, createRecorder, replayFixtures } from "@noverax/courier-mock/testing"

const recorder = createRecorder({ source: api, targetUrl: "https://staging.example.com" })
await recorder.client.users.getAll()

const fixtures = recorder.getFixtures() // or recorder.toHar()
const { client: replayed } = createMockClient<typeof api>({ scenario: replayFixtures(fixtures) })
  • The calls go to each endpoint's own method and path, at targetUrl or at the source's baseURL. To record through a Courier proxy — createCourierServer, or an app with Courier's handler — pass proxy: { url, source }: the proxy route's URL and the name the source has there.
  • mode is "record" by default. "passthrough" calls the backend and records nothing; "replay" answers from the fixtures given, without the network, and rejects a call it has no recording of; "record-missing" replays what fixtures has and records the rest.
  • sanitize(data, part) rewrites what is recorded — each call's variables (part is "variables") and each answer's body ("response") — never what the call returns. A recording made with it keeps no query in its URL, and replay passes a call's variables through the same function before looking for their recording; give replayFixtures and replayHar the sanitizer as their third argument for the same reason. Authorization, Proxy-Authorization, Cookie and Set-Cookie are never recorded. filter names the endpoints the recorder may call.

Exports

Entry PointDescription
@noverax/courier-mockdefineMocks, defineMockServer, defineFactory, defineScenario, MockHttpError
@noverax/courier-mock/testingcreateMockClient, createMockStore, createRecorder, replayFixtures, readRecordings, verifyRecordings — see Tests from Recorded Traffic
@noverax/courier-mock/servercreateMockServer — a standalone Hono-based mock server; persist: { file } keeps its store across restarts in an SQLite database it creates and migrates itself
@noverax/courier-mock/contractContract testing utilities
@noverax/courier-mock/managedrunMockWorker — the process Studio starts for each mock it runs