Tests & Mutation Lab

The Course
•
4 min read

The project has six grouped runtime tests and six compile-only negative usages. Runtime groups cover normalization/projection, malformed data, dispatch and independent subscriptions, snapshot mutation, disposal, and synchronous exception identity. The consumer also contains valid calls so a useless API that accepts nothing will fail checking.

Run npm test in the download. It builds declarations, checks consumer.ts through the public export, then runs Node tests through that same export. Never execute consumer.ts: its intentional invalid lines exist only for the checker.

The authoring checks unpack the actual ZIP into a temporary directory, compile it, run its demo and tests, then introduce targeted mutations. Dropping trimming, unsubscribe deletion, snapshot copying or the disposed publish guard must fail runtime evidence. Widening Listener’s return to void must make the async-listener directive unused.

Try one mutation at a time and restore it immediately. A failure caused by an unrelated syntax error does not demonstrate that your behavioral test protects the promised rule. Name the observable difference before editing.

Predict, check, and run

Write the output before running. Use Check types first, then Check & run. Types disappear at runtime: explain both what the compiler checks and what the code actually does. Each editor is an independent ES module; await asynchronous work explicitly.

Predict, then runTypeScript
const failure = new Error("listener failed");
let caught: unknown;

try {
  throw failure;
} catch (error) {
  caught = error;
}

console.log(caught === failure);

Enable JavaScript to edit and type-check this example.

Check your prediction
true

Predict what each valid mutation should change

Read the public consumer first: every expected-error line should isolate one relationship, while valid calls prove usability. Runtime groups then observe normalization, identity, snapshot order, disposal and thrown error identity. Neither layer can substitute for the other.

Use valid but wrong changes. A no-op unsubscribe should leave an extra callback; live iteration should expose a newly added listener too early. A parse error or a hang is not evidence that a specific contract assertion noticed the intended regression.

Boundary or caseWhat to inspect
Declaration consumerReject raw ids, wrong payloads and unsupported async callbacks
Registration and dispatchObserve independent duplicates and snapshot order
FailurePreserve exact thrown identity and stop later callbacks

Write the expected failed assertion before mutating. Restore source and rebuild declarations between experiments so an old dist artifact cannot contaminate the next observation. Keep the six valid/invalid consumer relationships distinct from runtime behavior groups.

Build it yourself

Exercise

Tests & Mutation Lab: practise

Write invoke(callback) that calls a synchronous callback without relabeling its exception. Assert the exact Error object escapes, then print identity verified. The test assertion must be outside the catch.

Your workspaceTypeScript
// Start from the stated contract. Add a valid call and an edge case.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

An error message alone does not prove that the original error survived.

Hint 2: a first step

Capture the thrown value as unknown and compare by object identity afterward.

Show one solution
One possible solutionTypeScript
function invoke(callback: () => undefined): void {
  callback();
}

const expected = new Error("Subscriber defect");
let actual: unknown;

try {
  invoke(() => {
    throw expected;
  });
} catch (error) {
  actual = error;
}

if (actual !== expected) {
  throw new Error("Original error lost");
}

console.log("identity verified");

Enable JavaScript to edit and type-check this example.

identity verified

Trace the implementation against the task before comparing styles. A passing example supports that case; use the changes below to challenge the wider contract.

Change it and test it

Replace invoke with a catch that throws a new Error having the same message. The identity test must fail. In the real project, locate the tests that distinguish invalid input from callback failure.

Was this article helpful?

Your response is saved on this device.