Testing the Public Contract

The Course
•
4 min read

The download has two test layers. contracts.ts contains four intentional invalid usages under @ts-expect-error and is included in normal compiler checking. catalogue.test.mjs runs the emitted JavaScript to verify normalization, issue paths, duplicate ids, state transitions and async dependency failures.

Run npm test after installing the pinned compiler. Its build step checks the type contracts before running five grouped runtime cases. Do not execute the emitted contracts.js file; it contains deliberately invalid operations that only exist as compile checks.

Challenge both layers. Removing readonly from pages should make a negative directive unused and fail the compiler. Removing duplicate-id validation or the request-id comparison should still compile but fail runtime tests. Restore every mutation afterward.

The schedule group connects the actual createLoader and reduce exports. It allocates ids before awaiting, then releases old/new transports in both orders. Old success and old failure must leave the current loading or ready state unchanged. This checks orchestration as well as individual reducer transitions.

The test strategy is intentionally small: it checks this library’s advertised behavior without snapshots of implementation internals. A new public feature needs corresponding evidence, not just more type aliases.

Predict, run, and explain

Write the output and trace the important state changes before checking it. Then run the program. Use Check types first; execution is allowed only when the real compiler accepts the source.

Predict, then runTypeScript
type Result<T> =
  | {
      ok: true;

      value: T;
    }
  | {
      ok: false;

      message: string;
    };

function read(result: Result<number>): number | null {
  return result.ok ? result.value : null;
}

if (read({ ok: true, value: 0 }) !== 0) {
  throw new Error("Zero lost");
}

if (read({ ok: false, message: "bad" }) !== null) {
  throw new Error("Failure lost");
}

console.log("2 runtime cases passed");

Enable JavaScript to edit and type-check this example.

Check your prediction
2 runtime cases passed

Connect direct transition tests to full request schedules

Decoder tests protect field truth; reducer tests protect transitions for already constructed actions. The schedule group connects real loaders and reducer calls so request ids survive actual awaiting. Keep these layers separate when interpreting coverage. A stale-result test after ready does not replace the case where a newer request is still loading.

Boundary or caseWhat to inspect
Positive normalized dataCheck projected output and unchanged source
Invalid field or duplicateReject with a specific path rather than partial success
Old and new completionCross both orders with old success and old failure

For each deliberate mutation, name its distinguishing assertion before changing source. Then weaken readonly and explain why the negative consumer should fail even when the same runtime fixtures still pass. Restore and rebuild between experiments.

Your turn

Exercise

Testing the Public Contract: practise

Write a typed function that returns a copied record with trimmed title. Assert the original text stays padded, the result is trimmed and an unrelated input field is not copied. Print projection verified.

Your workspaceTypeScript
// The return shape is not sufficient evidence; compare actual runtime data.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

An explicit new object controls projection and ownership.

Hint 2: a first step

Construct only the modeled title property after trimming it.

Show one solution
One possible solutionTypeScript
type Book = { title: string };

function clean(book: Book): Book {
  return { title: book.title.trim() };
}

const source = { title: " Types ", privateNote: true };
const result = clean(source);

if (
  result.title !== "Types" ||
  source.title !== " Types " ||
  "privateNote" in result
) {
  throw new Error("Projection failed");
}

console.log("projection verified");

Enable JavaScript to edit and type-check this example.

projection verified

Explain why the implementation meets the stated contract. Which part is enforced by the language or checker, and which part needs behavioral evidence?

Change it and test it

Run all project cases before changing source. Then introduce each documented mutation and identify the layer that catches it. Explain why a type test may pass while data validation is wrong, and why runtime tests cannot prove a public signature rejects an invalid TypeScript caller.

Was this article helpful?

Your response is saved on this device.