You are building a Counterfact mock with stateful logic in a _.context.ts file and want to keep that logic reliable as the mock grows.
Handler files are intentionally thin — they read from context, return a response, and contain minimal business logic. The context class, on the other hand, accumulates meaningful logic: lookups, mutations, invariants, and derived data. As the context grows, bugs in it break every handler that depends on it, but nothing makes those bugs visible until a manual test fails.
Write unit tests for the context class. Test its methods directly, without starting the server. Leave handler files untested: they are expected to be edited freely and contain no logic worth automating against.
Given a context class:
// api/routes/_.context.ts
import type { Pet } from "../types/components/pet.types.js";
export class Context {
private pets = new Map<number, Pet>();
private nextId = 1;
add(data: Omit<Pet, "id">): Pet {
const pet = { ...data, id: this.nextId++ };
this.pets.set(pet.id, pet);
return pet;
}
get(id: number): Pet | undefined { return this.pets.get(id); }
list(): Pet[] { return [...this.pets.values()]; }
remove(id: number): void { this.pets.delete(id); }
}Write tests that exercise the class directly:
import { Context } from "../api/routes/_.context.js";
describe("Context", () => {
let context: Context;
beforeEach(() => {
context = new Context();
});
it("assigns sequential ids", () => {
const first = context.add({ name: "Fluffy", status: "available", photoUrls: [] });
const second = context.add({ name: "Rex", status: "available", photoUrls: [] });
expect(first.id).toBe(1);
expect(second.id).toBe(2);
});
it("returns undefined for a missing id", () => {
expect(context.get(99)).toBeUndefined();
});
it("removes a pet", () => {
const pet = context.add({ name: "Fluffy", status: "available", photoUrls: [] });
context.remove(pet.id);
expect(context.get(pet.id)).toBeUndefined();
expect(context.list()).toHaveLength(0);
});
});The context class has no dependency on Counterfact internals — no server, no $ object, no HTTP machinery — so the tests are fast, isolated, and easy to write.
- Context logic is covered by fast unit tests that catch regressions without starting a server.
- Handlers stay thin and free of business logic; they are not tested and can be modified without worrying about breaking tests.
- The split creates a clear seam: if a handler is doing anything complex, it is a signal to move that logic into the context.
- Tests written against the context class remain valid after handler rewrites or spec changes that do not alter the context's interface.
- Mock APIs with Dummy Data — the context pattern the tests above are written for
- AI-Assisted Implementation — unit-test the context the agent generates to keep it reliable
- Reference Implementation — a reference implementation accumulates significant context logic that benefits from test coverage