Testing TypeScript Types: Part 2 (Advanced Solutions)

Picture of the author
Nicolas CharpentierAugust 09, 2023
3 min read
Testing TypeScript Types: Part 2 (Advanced Solutions)

The first part of this series covered the preamble as well as an early solution used to test TypeScript types, it had some flaws, like not being run as part of a test suite.

Let's cover some advanced solutions that will make the integration of type testing into your project a breeze.

expect-type

πŸ“š https://github.com/mmkal/expect-type (193 ⭐ β€” 67,276 weekly downloads)

Compile-time tests for types. Useful to make sure types don't regress into being overly-permissive as changes go in over time.

My ✨ favorite ✨ and also used by Type Fest, Apollo Client, Prisma Client, and tRPC.

Quite easy to use and could be used directly within your existing test filesβ€”or any other type-checked files.

import { expectTypeOf } from 'expect-type';
import { foo, bar } from '../foo';

test('foo types', () => {
  // make sure `foo` has type {a: number}
  expectTypeOf(foo).toMatchTypeOf<{ a: number }>();

  // make sure `bar` is a function taking a string:
  expectTypeOf(bar).parameter(0).toBeString();
  expectTypeOf(bar).returns.not.toBeAny();
});

If we follow the test case from the preamble, we can write the following test:

convertNewUnionToOldEnum.test.ts
import { expectTypeOf } from 'expect-type';
import { convertNewUnionToOldEnum, type NewUnion, type OldEnumAsUnion } from '../convertNewUnionToOldEnum';

describe('convertNewUnionToOldEnum', () => {
  it('should cast to an equivalent type', () => {
    expectTypeOf<NewUnion>().toEqualTypeOf<OldEnumAsUnion>();
  });
});

πŸ”— TypeScript Playground.

tsd

πŸ“š https://github.com/SamVerschueren/tsd (2,100 ⭐ β€” 118,463 weekly downloads)

Check TypeScript type definitions.

This tool lets you write tests for your type definitions (i.e. your .d.ts files) by creating files with the .test-d.ts extension.

Used by Type Fest, Puppeteer, Socket.IO, Bun, VueJS, and Prisma Client.

index.test-d.ts
import {expectType} from 'tsd';
import concat from '.';

expectType<string>(concat('foo', 'bar'));
expectType<string>(concat(1, 2));

ts-expect

πŸ“š https://github.com/TypeStrong/ts-expect (175 ⭐ β€” 95,554 weekly downloads)

Checks values in TypeScript match expectations.

TS Expect exports a function, named expectType, that does nothing at all. Instead, it depends on the TypeScript compiler and a generic to test the type of a "value" passed to expectType is assignable to its generic in the type system.

Used by Chart.js, Prettier, Marked, and Jotai.

Quite similar to the early solution I shared in part 1:

import { expectType } from 'ts-expect';

expectType<string>('test');
expectType<number>(123);
expectType<number>('test'); // Compiler error!

It even has the equivalent of isExact:

import { expectType, TypeEqual } from 'ts-expect';
import { add } from './adder';

expectType<number>(add(1, 2));
expectType<TypeEqual<number, ReturnType<typeof add>>>(true);
expectType<TypeEqual<[number, number], Parameters<typeof add>>>(true);

type-plus

πŸ“š https://github.com/unional/type-plus (264 ⭐ β€” 2,903 weekly downloads)

Additional types and types adjusted utilities for TypeScript.

This is the most complicated one here, but I thought it was worth sharing because it's way bigger and more complete than the other alternatives. type-plus is in fact more than 200 type utilises for TypeScript, useful not only for tests, but also at the application level.

Here's an example with assertType that provides a generic assertion function:

const s: unknown = 1;

// TypeError: subject fails to satisfy s => typeof s === 'boolean'
assertType<boolean>(s, (s) => typeof s === 'boolean');