User manual

Test the Context, Not the Handlers

Keep route handlers small: read the request, call a context method, and return a response. Put lookups, updates, and other shared logic in the context class, where you can test it without starting a server.

Test context methods directly

This example uses the same add, get, list, and remove methods as the pet context in the state guide, with generated types from the first-ten-minutes Petstore contract. Given that context class:

// api/routes/_.context.ts
import type { Schemas } from "../types/index.js";

type Pet = Schemas["Pet"];

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.

Keep shared logic in the context

If a handler needs more than request parsing and response building, move that logic into a context method and test it there. Those tests can stay the same when you rewrite handlers or change parts of the spec that do not affect the context. Use HTTP integration tests to check the responses your client receives.