# @toad-contracts/testing

> Testing utilities for mocking @toad-contracts HTTP responses with mockttp and msw

Latest version **1.0.0** (published 2026-09-18) · MIT license · 0 weekly downloads

## Install

```sh
npm install @toad-contracts/testing
pnpm add @toad-contracts/testing
yarn add @toad-contracts/testing
bun add @toad-contracts/testing
```

## Health

**Score 75/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; has provenance; recently updated; high maintenance score; high quality score.

Warnings: low downloads.

## Facts

| | |
|---|---|
| Version | 1.0.0 |
| Published | 2026-09-18 |
| First published | 2026-06-26 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 2 |
| Unpacked size | 54.4 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Provenance | attested (GitHub Actions) |
| GitHub stars | 0 |
| Maintainers | kibertoad |
| Keywords | api, backend, contract, contracts, frontend, mock, mocking, mockttp, msw, standard-schema, testing |

## Links

- npm: https://www.npmjs.com/package/@toad-contracts/testing
- Repository: https://github.com/kibertoad/toad-contracts
- npm.io page: https://npm.io/package/@toad-contracts/testing

## Dependencies (2)

- [@toad-contracts/core](https://npm.io/package/@toad-contracts/core.md) 1.0.0
- [@standard-schema/spec](https://npm.io/package/@standard-schema/spec.md) ^1.1.0

## Alternatives

- [pagerjs](https://npm.io/package/pagerjs.md) — 60 weekly downloads
- [whistle.savefor-mock](https://npm.io/package/whistle.savefor-mock.md) — 4 weekly downloads
- [@crvouga/mockingbird-service-junction](https://npm.io/package/@crvouga/mockingbird-service-junction.md) — 0 weekly downloads
- [@crvouga/mockingbird-service-medplum](https://npm.io/package/@crvouga/mockingbird-service-medplum.md) — 0 weekly downloads
- [@crvouga/mockingbird-service-genebygene](https://npm.io/package/@crvouga/mockingbird-service-genebygene.md) — 0 weekly downloads

## Recent versions

- 1.0.0 (latest) — 2026-09-18
- 0.3.2 — 2026-06-27
- 0.3.1 — 2026-06-27
- 0.3.0 — 2026-06-27
- 0.1.0 — 2026-06-26

## README

# @toad-contracts/testing

Testing utilities for mocking HTTP responses defined with
[`@toad-contracts/core`](https://github.com/kibertoad/toad-contracts) (or any adapter built on it,
such as `@toad-contracts/valibot`). Two helpers register mock rules from a contract and validate the
response body through the contract's Standard Schema:

| Helper                     | Backend                                           | Use case                      |
| -------------------------- | ------------------------------------------------- | ----------------------------- |
| `ApiContractMockttpHelper` | [mockttp](https://github.com/httptoolkit/mockttp) | server-side integration tests |
| `MswHelper`                | [msw](https://mswjs.io)                           | frontend tests                |

`mockttp` and `msw` are optional peer dependencies; install whichever you use.

## Table of contents

- [ApiContractMockttpHelper](#apicontractmockttphelper)
  - [mockResponse](#mockresponse)
  - [Response kinds](#response-kinds)
  - [Range and wildcard status keys](#range-and-wildcard-status-keys)
  - [Type safety](#type-safety)
- [MswHelper](#mswhelper)
  - [mockResponse](#mockresponse-1)
  - [mockSseStream](#mockssestream)
- [formatSseResponse](#formatsseresponse)
- [validateResponseBody](#validateresponsebody)

## ApiContractMockttpHelper

Mock HTTP responses in mockttp-based tests.

### Setup

```ts
import { getLocal } from "mockttp";
import { ApiContractMockttpHelper } from "@toad-contracts/testing";

const mockServer = getLocal();
const helper = new ApiContractMockttpHelper(mockServer);

beforeEach(() => mockServer.start());
afterEach(() => mockServer.stop());
```

### mockResponse

Registers a mock rule for the given contract. `responseStatus` is the concrete numeric HTTP status
code the mock sends. It also selects which schema is used: the helper looks up the contract entry
with exact → range → `'default'` precedence, so a contract with only a `'2xx'` key accepts any
`responseStatus` in 200-299.

```ts
import { defineApiContract } from "@toad-contracts/core";
import { withObjectKeys } from "@toad-contracts/valibot";
import { object, string } from "valibot";

const contract = defineApiContract({
  method: "get",
  pathResolver: () => "/users",
  responsesByStatusCode: { 200: object({ id: string() }) },
});

await helper.mockResponse(contract, {
  responseStatus: 200,
  responseJson: { id: "1" },
});
```

The body is validated and stripped through the contract's Standard Schema before being sent. Path
params are required when the contract declares `requestPathParamsSchema`:

```ts
const getUser = defineApiContract({
  method: "get",
  requestPathParamsSchema: withObjectKeys(object({ userId: string() })),
  pathResolver: ({ userId }) => `/users/${userId}`,
  responsesByStatusCode: { 200: object({ id: string() }) },
});

await helper.mockResponse(getUser, {
  pathParams: { userId: "42" },
  responseStatus: 200,
  responseJson: { id: "42" },
});
```

### Response kinds

`params` is a discriminated union on `responseStatus`. The body fields required for a status code
are inferred from the contract's response entry for that code:

| Body declared by the entry        | Required field                         |
| --------------------------------- | -------------------------------------- |
| a Standard Schema (JSON)          | `responseJson: StandardSchemaV1.Input` |
| `blobBody()` / `blobResponse(ct)` | `responseBlob: string \| Uint8Array`   |
| `sseBody()` / `sseResponse(...)`  | `events: { event; data }[]`            |
| `noBodyResponse()`                | _(none)_                               |

A content map declaring several bodies asks for one field per kind, so a dual-mode (JSON + SSE)
status code requires both `responseJson` and `events`, and the mock answers by `accept` the way the
real route does. An entry that also sets `allowNoBody: true` makes every body field optional: omit
them all to mock the empty response, or supply just one to mock only that body.

When a status code declares several variants of one kind (e.g. `application/json` and
`application/json+01`), pass `contentType` to name the media type the mock should serve. It is
matched the way the client matches a response `content-type` — parameters stripped, case ignored —
and must name a media type the status code declares; anything else throws, so a typo fails the test
instead of silently mocking an empty body. Without it, the first media type the contract declares
wins.

```ts
await helper.mockResponse(getReport, {
  responseStatus: 200,
  contentType: "application/pdf",
  responseBlob: "%PDF-",
});
```

For SSE contracts, the mock replies with a `text/event-stream` body built from `events`:

```ts
import { sseResponse } from "@toad-contracts/core";

const sse = defineApiContract({
  method: "get",
  pathResolver: () => "/events/stream",
  responsesByStatusCode: {
    200: sseResponse({ completed: object({ totalCount: number() }) }),
  },
});

await helper.mockResponse(sse, {
  responseStatus: 200,
  events: [{ event: "completed", data: { totalCount: 1 } }],
});
```

For dual-mode contracts — one status code declaring both `application/json` and
`text/event-stream` — the mock routes on the request's `Accept` header: `text/event-stream`
receives the SSE stream, everything else receives the JSON body. Both `events` and `responseJson`
are required.

### Range and wildcard status keys

Contracts may use range keys (`'1xx'`-`'5xx'`) or `'default'` instead of exact codes. Pass any
concrete numeric code covered by that range as `responseStatus`; the helper resolves the entry with
the same exact → range → `'default'` precedence as the runtime client.

### Type safety

`MockResponseParams<TContract>` is exported for typing the params object separately:

```ts
import type { MockResponseParams } from "@toad-contracts/testing";

function mockUser(params: MockResponseParams<typeof getUserContract>) {
  return helper.mockResponse(getUserContract, params);
}
```

## MswHelper

The msw counterpart. Construct it with a base URL, then register handlers on an msw `SetupServer`.
`mockResponse` takes the same `MockResponseParams` as the mockttp helper.

```ts
import { setupServer } from "msw/node";
import { MswHelper } from "@toad-contracts/testing";

const server = setupServer();
const helper = new MswHelper("http://localhost:8080");

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
```

### mockResponse

```ts
helper.mockResponse(contract, server, {
  responseStatus: 200,
  responseJson: { id: "1" },
});
```

### mockSseStream

Returns an `SseEventController` for emitting SSE events on demand instead of all at once. Works with
SSE and dual-mode contracts; for dual-mode contracts, non-SSE requests receive `responseJson`.

```ts
const controller = helper.mockSseStream(sseContract, server);

const response = await fetch("http://localhost:8080/events/stream");

controller.emit({ event: "completed", data: { totalCount: 1 } });
controller.close();
```

Event names and data shapes are inferred from the contract's SSE schemas.

## formatSseResponse

A standalone helper for manual SSE body formatting:

```ts
import { formatSseResponse } from "@toad-contracts/testing";

const body = formatSseResponse([{ event: "completed", data: { totalCount: 1 } }]);
// "event: completed\ndata: {\"totalCount\":1}\n\n"
```

## validateResponseBody

Validates a value against a Standard Schema and returns the parsed output (unknown keys stripped,
transforms applied), the synchronous validation the mock helpers use internally. It is re-exported
for tests that need the same check directly. A schema that validates asynchronously is unsupported
and throws a `TypeError`, since the mock helpers buffer the body synchronously.

```ts
import { validateResponseBody } from "@toad-contracts/testing";
import { object, string } from "valibot";

validateResponseBody(object({ id: string() }), { id: "1", extra: "dropped" }); // { id: "1" }
```

---
_Source: https://npm.io/package/@toad-contracts/testing · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
