Skip to content

Contracts and verification

rocambille edited this page May 4, 2026 · 4 revisions

Summary: This page describes the StartER verification strategy, based on Contract-Driven Development for the API and React Testing Library for the interface.

Verification in the AI era

In StartER, tests are not just for catching bugs; they serve as AI guardrails and living documentation. By defining the API behavior declaratively, you provide a clear specification that AI agents can use to generate and verify code.

Tip

Run the full suite with:

npm run test

API integration: contracts as AI instruction sets

Instead of writing imperative test files, StartER uses a contract-based approach.

The tests/contracts.ts file

This file is the single source of truth for your API. Because it is a structured TypeScript object, AI agents can parse it to understand exactly what they need to build.

// Example contract for reading an item
const contracts = {
  items: {
    read: {
      method: "get",
      path: "/api/items/1",
      cases: {
        success: {
          request: {},
          response: { status: 200, body: { id: 1, title: "My Item", user_id: 1 } },
        },
        not_found: {
          specialPath: "/api/items/NaN",
          request: {},
          response: { status: 404, body: {} },
        },
      }
    }
  }
};

Why this is AI-native:

  • Context-rich: an AI agent reading this file knows all handled error cases (401, 404, 400) at a glance.
  • Self-correction: if the AI generates an action that returns a 404 when it should return a 200, the contract test will fail. You can then feed the failure back to the AI for instant correction.
  • Consistency: the test runner (tests/api/contracts.test.ts) ensures that every contract is tested the same way, preventing AI from introducing "test drift."

Isolation and performance (SQLite)

A major strength of StartER lies in using in-memory SQLite for verification:

  • Perfect isolation: each API test starts with a fresh, independent database in memory (:memory:). One test can never corrupt another.
  • Schema compliance: the schema (schema.sql) is applied before each scenario. You are always testing against the real structure.
  • Speed: near-instant execution because no disk access is required.

React tests

React tests are located in tests/react/ and focus on user behavior.

  • React Testing Library: interact with the DOM semantically (getByRole, userEvent.click).
  • Data mocks: the cache system is simulated using contract data, allowing the UI to be tested in isolation from the network.

Example component test:

it("should display an item", async () => {
  await renderWithStub(
    "/items/:id",
    ItemShow,
    [`/items/${allItems[0].id}`],
    { me: fooUser },
  );

  await screen.findByRole("heading", { level: 1, name: allItems[0].title });

  // Verifies the component "called" the contract
  expectContractCall("items", "read", "success");
});

Best practices for AI verification

  • Contract first: always update contracts.ts before asking the AI to implement the logic.
  • Realistic data: use data close to reality in your mocks so the AI has good examples of what the data looks like.
  • Snake case: name your scenarios in snake_case (e.g., bad_request, unauthorized) for consistency.

See also

Clone this wiki locally