Type Checks & Runtime Tests

The Course
•
4 min read

A type check verifies that your code uses its declared contracts consistently. A runtime test verifies an observable behavior for a chosen input. Your project needs both. A calculation can return the wrong number with no compiler diagnostic, and a parser can claim a shape without checking it properly.

A tiny assertion helper can compare an actual primitive value with an expected one and throw Error when they differ. A failed assertion is a runtime failure, separate from compiler errors. Include a label so the failing case is easy to locate.

For the planner, test a normal mixed list, an empty list, a completed zero-minute session, and an all-incomplete list. For the parser, test syntax errors, non-list JSON, every missing field, wrong field types, blank text and negative minutes. A few chosen boundaries provide better evidence than many copies of the same happy path.

Mutation checking is a useful self-review habit: temporarily remove a validation condition or count incomplete rows too. At least one existing test should fail. If every test still passes, add a case that distinguishes correct and broken behavior. Then restore the implementation.

The downloadable project’s tests use Node’s built-in test runner and strict assertions on the emitted JavaScript. The code below uses a small self-contained helper so it also runs in the browser editor. Both approaches test behavior; neither replaces the compiler.

Predict, check, then run

Before running, write down the output and explain which information the checker knows. Click Check types first, then Check & run.

Predict, then runTypeScript
function assertEqual(actual: number, expected: number, label: string): void {
  if (actual !== expected) {
    throw new Error(`${label}: expected ${expected}, got ${actual}`);
  }
}

function total(minutes: readonly number[]): number {
  let result = 0;

  for (const value of minutes) {
    result += value;
  }

  return result;
}

assertEqual(total([10, 20]), 30, "two sessions");
assertEqual(total([]), 0, "empty");
console.log("2 tests passed");

Enable JavaScript to edit and type-check this example.

Check your prediction
2 tests passed

Separate caller evidence from runtime evidence

A diagnostic can prove a particular caller is rejected, but not that a decoder trims text or a report sums only completed minutes. Runtime success cannot maintain the whole public type surface either. Read positive assertions as well as rejection cases; a parser rejecting every input should not pass a useful suite.

Boundary or caseWhat to inspect
Wrong caller field typeCompiler rejects it before execution
Malformed imported valueRuntime validation rejects external data
Wrong arithmeticExpected values distinguish record count from minutes

Remove one runtime predicate without changing its signature and identify the behavior test that fails. Then weaken a public type and identify the contract test that fails. Restore each change and explain why the other layer cannot replace that evidence.

Your turn

Exercise

Type Checks & Runtime Tests: practise

Write completedMinutes for readonly records containing minutes:number and done:boolean. Add runtime assertions for mixed rows, an empty list and a completed zero-minute row. Print 3 tests passed only after the assertions finish.

Your workspaceTypeScript
// Define the function and a small numeric assertion helper.

Enable JavaScript to edit and type-check this example.

Hint 1: the idea

Choose cases that would catch accidentally counting incomplete rows.

Hint 2: a first step

Use a mixed case with different completed and incomplete minute values.

Show one solution
One possible solutionTypeScript
type Session = {
  minutes: number;

  done: boolean;
};

function completedMinutes(sessions: readonly Session[]): number {
  let total = 0;

  for (const session of sessions)
    if (session.done) {
      total += session.minutes;
    }

  return total;
}

function assertEqual(actual: number, expected: number, label: string): void {
  if (actual !== expected) {
    throw new Error(`${label}: expected ${expected}, got ${actual}`);
  }
}

assertEqual(
  completedMinutes([
    { minutes: 25, done: true },
    { minutes: 40, done: false }
  ]),
  25,
  "mixed"
);
assertEqual(completedMinutes([]), 0, "empty");
assertEqual(completedMinutes([{ minutes: 0, done: true }]), 0, "zero");
console.log("3 tests passed");

Enable JavaScript to edit and type-check this example.

3 tests passed

Explain the types at the function boundary and the runtime checks, if any. Compare your approach before copying this answer.

Change it and explain it

Remove the done condition temporarily. Check types still succeeds, but the mixed test should fail. Restore it. For the parser, name a specific test that would catch removing the nonblank-topic check. Write down what evidence a passing suite still cannot provide about every possible input.

Was this article helpful?

Your response is saved on this device.