Skip to content

PRODUCTION SKILLS · LESSON 19 OF 24

Test types and runtime behavior

Verify compile-time contracts and business behavior separately.

  • assertions
  • @ts-expect-error
  • regression tests
  • noEmit

Your runnable checkpoint

Extract into a new practice folder. Each download includes complete source, package.json, tsconfig.json and an offline lesson. Use Node.js 22 or 24. Each checkpoint is independent.

Install with npm install. Run npm run check, then npm run build. Terminal lessons use npm start; browser lessons use npm run serve and http://127.0.0.1:4173.

1. Understand the concepts

Type checking finds invalid combinations of declared types. Runtime tests verify the behavior of actual JavaScript values. You need both: a function can be perfectly typed and still calculate the wrong answer.

@ts-expect-error asserts that the next line must produce a type error. If the compiler no longer finds one, it reports an unused directive. This is useful in focused type tests; @ts-ignore would suppress diagnostics without detecting that change.

Put intentionally invalid calls inside an uncalled function when the emitted file is executed. The compiler still checks the body, but the invalid calls do not run. Do not suppress errors in application code just to make a build pass.

This small checkpoint uses a throwing assertion helper instead of a testing dependency. A failed expectation exits the program with an error. In larger projects, a test runner adds isolation, reporting, coverage and fixtures.

2. Build and run

These are complete files. Preserve the paths shown below, including the src directory. The package and compiler configuration are included at the end.

src/main.ts

src/main.ts
function sum(a: number, b: number): number { return a + b; }
function assert(condition: boolean, message: string): void {
  if (!condition) throw new Error(message);
}
function typeTests(): void {
  // @ts-expect-error Strings must not be accepted by sum.
  sum("2", 3);
}
void typeTests;
assert(sum(2, 3) === 5, "Positive numbers");
assert(sum(-2, 2) === 0, "Negative numbers");
assert(sum(0, 0) === 0, "Zero");
console.log("3 runtime checks passed");

package.json

package.json
{
  "name": "edupoly-typescript-testing-types-and-behavior",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "check": "tsc --noEmit",
    "build": "tsc",
    "start": "node dist/main.js"
  },
  "devDependencies": {
    "typescript": "5.9.3"
  }
}

tsconfig.json

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "lib": [
      "ES2022",
      "DOM"
    ],
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "exactOptionalPropertyTypes": true,
    "noEmitOnError": true,
    "verbatimModuleSyntax": true,
    "rootDir": "src",
    "outDir": "dist",
    "sourceMap": true,
    "types": [],
    "moduleDetection": "force"
  },
  "include": [
    "src/**/*"
  ]
}

3. Trace the code and check the result

  1. The error directive is checked by npm run check.
  2. The intentionally wrong call is never executed.
  3. Each runtime assertion tests a behavior rather than the shape of the implementation.

Expected result

3 runtime checks passed

4. Practice independently

Temporarily change sum to subtraction and observe which check catches it.

Show the explanation

npm run check still passes because subtraction returns number. npm run build followed by npm start fails the positive-numbers assertion. Restore addition afterwards.

When something goes wrong

If tsc is missing, run npm install in the folder containing package.json. If the output looks old, rebuild before running. Read the first compiler diagnostic and check the indicated field or argument. For module errors, preserve the .js extensions in imports. Browser examples must run through the included local server, not directly as TypeScript files.

Read the related official TypeScript documentation ↗