thecodex.expert · The Codex Family of Knowledge
TypeScript

Testing TypeScript

A test suite can pass completely while type errors sit right next to it — test runners transpile and run your code without necessarily type-checking it first.

TypeScript 6.0 Jest / Vitest Last verified:
Canonical Definition

TypeScript itself has no test runner — Jest and the newer Vitest (built on Vite, popular for its speed) are the dominant choices in the ecosystem, both running .ts test files by transpiling them on the fly rather than requiring a separate tsc build step first. A crucial, often-missed detail: transpiling and type-checking are separate concerns, so a test suite can run and pass completely while the same code has real type errors that a full tsc run would catch — running tsc --noEmit as a separate CI step is the common way to close that gap.

A basic test with Vitest

Vitest's API closely mirrors Jest's, and both run .ts files directly — no separate compile step needed before running tests.

TypeScriptmath.test.ts
import { describe, it, expect } from "vitest";
import { add } from "./math";

describe("add", () => {
    it("adds two numbers", () => {
        expect(add(2, 3)).toBe(5);
    });
});

Type-checking is a SEPARATE step from running tests

This is the detail newcomers most often miss: a test suite can pass in full while the surrounding code has genuine type errors, since the test runner only transpiles (strips types) rather than fully type-checking. Running tsc --noEmit as its own CI step closes this gap.

TypeScriptpackage.json
{
  "scripts": {
    "test": "vitest run",
    "typecheck": "tsc --noEmit",
    "ci": "npm run typecheck && npm run test"
  }
}

Typing mocks and test doubles

Type-safe mocking keeps a mocked dependency's shape in sync with the real one — if the real function's signature changes, a typed mock that no longer matches becomes a visible compile error, rather than a silent runtime mismatch discovered only when the test happens to fail.

TypeScriptuser_service.test.ts
import { vi, it, expect } from "vitest";

interface UserApi {
    getUser(id: number): Promise<{ name: string }>;
}

const mockApi: UserApi = {
    getUser: vi.fn().mockResolvedValue({ name: "Alice" }),   // must match UserApi's shape
};

it("fetches a user", async () => {
    const user = await mockApi.getUser(1);
    expect(user.name).toBe("Alice");
});

Sources

1
Vitest documentation, vitest.dev, and Jest documentation, jestjs.io, "Getting Started with TypeScript".