Sole engineer · 2025 · live
MockMaster
Record real HTTP responses, commit them as plain JSON, replay them deterministically in tests.
- TypeScript
- pnpm
- Turborepo
- changesets
The problem
Test mocks drift. Someone hand-writes a fixture from what an API returned in 2023, the API changes, and the suite keeps passing against a response shape that no longer exists. The alternatives are worse: hitting the real service makes tests slow and flaky, and recording into an opaque cassette format means nobody can read, review, or edit what the test is actually asserting against.
What I built
A framework-agnostic mocking library, published as five npm packages under
@mockmaster. It captures real HTTP responses — or generates them from an
OpenAPI 3.0 spec — and stores them as plain JSON scenario files that live
beside your tests and go through code review like any other source. Replay is
deterministic, with guides for Vitest, Jest, Playwright, and Storybook, so
the same scenario file drives a unit test and a browser test without
translation.
It is a TypeScript monorepo on pnpm and Turborepo, versioned with changesets, with CI running on Node 18, 20, and 22. 184 tests.
Decisions
The recorded path doubles as the match pattern. A recording of
/users/:id replays for any id, because the path you captured is the
pattern the matcher uses — there is no second, separate routing config to
keep in sync with your fixtures. One artifact, one source of truth. What this
gives up is precision: you cannot record two different responses for
/users/1 and /users/2 and expect the library to distinguish them from the
path alone. That is a real limitation, and it is the price of never having a
fixture and a route table disagree.
Scenarios are plain JSON committed beside the tests. Not a binary cassette, not a database, not a running proxy with its own lifecycle — files a human can open in a diff and a reviewer can object to. The library is functions over that data, which is what makes replay deterministic and makes the same scenario portable across four test runners. Given up: everything a stateful recording server buys you. There is no live inspector UI, no mutation of a recording in flight, and large captures show up as large diffs in your repository.
Shipping a whole release for one conversion function. OpenAPI path
templates use {id}; the matcher speaks :id. Every scenario generated from
a spec therefore matched nothing, silently — the request fell through, the
test saw no mock, and the failure surfaced somewhere unrelated. The 1.0.5
release of @mockmaster/cli is that one conversion function —
convertPathFormat(), which rewrites OpenAPI {parameter} syntax to Express
:parameter syntax during scenario generation. I chose to cut a release for
a single function rather than bundle the fix into the next feature, because
the bug's signature was silence, and every day it stayed out was another
generated scenario that looked fine and did nothing. The cost is release
noise: a changeset and a version bump for what is arguably a typo in a
translation layer.
Hard part
The hard part was not a technical mechanism. It was the ordering: the documentation was written first, as the specification, and then the implementation was audited against it.
Writing the docs first is easy to say and easy to abandon. The moment the code disagrees with the docs — and it always does, in a dozen small places at once — the natural move is to edit the sentence. It takes ten seconds and nobody notices. Do it enough times and the docs are no longer a spec; they are a changelog of whatever the code happened to become.
So I made the disagreement a discipline instead of a decision. Before each release, I audited the implementation against the docs function by function — every name, every signature, every parameter that did not match — and closed each gap explicitly rather than quietly editing the sentence to fit whatever the code happened to do.
Then the resolution went the uncomfortable way: the code was renamed to match
the documentation, not the reverse. That is still visible in the source as
export { parsePathParams as extractParams } — the internal name I actually
wrote, aliased out under the name I had promised readers. Keeping the alias
rather than doing a sweeping rename kept the internal call sites honest about
their own history, and cost one line of indirection at the package boundary.
The reason this matters more than it sounds: this library's entire value is that the mock matches reality. A project whose own docs do not match its own exports has no standing to make that claim. The audit was the only way to be able to say the documentation is accurate and mean it as a verified property rather than a hope.
Outcome
Public source, five packages published to npm on 11/12/2025, and live docs at mockmaster.org — the one project in this portfolio a reader can verify end-to-end in about thirty seconds without taking my word for anything. 184 tests, green on three Node versions. My own beta suite reports 95.7% (45 of 47); that number is self-run, not third-party, and should be read as such.