Skip to content

What it is

tsd tests your TypeScript type definitions, asserting that types resolve as intended and that invalid usage is correctly rejected.

Write assertions in a .test-d.ts file using expectType, expectError and friends. tsd type-checks them and fails when an assertion does not hold.

Installation

npm install --save-dev tsd

Getting started

The smallest useful thing you can do with it, and what each part means.

Asserting a public API's types
import { expectType, expectError, expectAssignable } from 'tsd';
import { pick } from './index';

const user = { id: 1, name: 'Ada', password: 'secret' };

// The return type must contain exactly the requested keys.
expectType<{ id: number; name: string }>(pick(user, ['id', 'name']));

// Requesting a key that does not exist must be rejected.
expectError(pick(user, ['nope']));

expectAssignable<object>(pick(user, ['id']));
expectType is exact, not assignable — widening the return to `object` fails the test. That strictness is the point: it catches accidental loss of precision.

Advanced usage

Where the library earns its place over a simpler alternative.

Catching an accidental any
import { expectNotAny, expectNotType } from 'tsd';

expectNotAny(parseConfig('{}'));

// Guard against a refactor widening a literal union to string.
expectNotType<string>(getRole());
expectType<'admin' | 'member'>(getRole());

// In package.json:
// { "scripts": { "test": "tsd && vitest run" } }
expectNotAny is the highest-value assertion for a library. An `any` leaking into a public signature disables checking for every consumer and nothing else will flag it.

Errors and fixes

The failures you are most likely to hit, and what actually resolves them.

Cannot find module for the package
tsd resolves the types field in package.json. Build the declarations first, or point it at the source with a tsd config.
expectType fails on seemingly identical types
It is an exact identity check. A readonly modifier or an optional marker counts as different — use expectAssignable when that is acceptable.

Best practices

  • Run tsd in CI alongside runtime tests — type regressions are invisible otherwise.
  • Assert with expectNotAny on every public entry point.
  • Use expectError to prove that misuse is actually rejected.
  • Keep type tests next to the public API they cover, not scattered.

Background

Why it exists, and what it was reacting to.

Written by Sindre Sorhus for library authors: a package's types are part of its public API, and without tests a refactor can silently widen a type to any while every runtime test still passes.