← All Articles

How to test your types

Matt Pocock
Matt Pocock

If you're working on library code, then you definitely want to be testing your types. Types are a crucial part of your API that you're exposing to users, and you want to make sure that your logic on the type level works.


One approach you can take is testing your types with vitest - an extremely powerful and popular test runner based on Vite. Vitest is already widely used for testing runtime code, so why not use it for testing your types too? Vitest ships with several APIs that let you test types.

import { assertType, expectTypeOf } from "vitest";
import { mount } from "./mount";

test("my types work properly", () => {
  expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>();

This is pretty cool and having it built into a test runner is really valuable, because it lets you colocate your type tests with your runtime tests.

Rolling your own

But if you're not using vitest, you can roll your own solution.

Let's say you're testing the type of something. You can use type helpers like Expect and Equal to be really specific with the type that you're trying to narrow to.

type Expect<T extends true> = T;
type Equal<X, Y> = (<T>() => T extends X ? 1 : 2) extends <
>() => T extends Y ? 1 : 2
  ? true
  : false;

// Example

const identityFunc = <T>(arg: T) => arg;

it("Should return whatever you pass in", () => {
  const test1 = identityFunc("hello");

  type test = Expect<Equal<typeof test1, "hello">>;

If you get an error, it'll show up as a red line in your IDE. As part of your test suite, you can then run tsc (the TypeScript CLI) on the entire test suite and check if any of your type tests fail.

You can also check that something doesn't work in TypeScript by using ts-expect-error. This is a special comment that you can add on a line to look at the next line. It'll fail if it doesn't see an error on the next line. You can use this to check if something is allowed or not and assert that an error is thrown in certain situations.

const myFunc = <T extends string>(arg: T) => {};

it("Should not accept a number", () => {
  // @ts-expect-error

It's not exactly perfect since it doesn't let you be that granular - you can't check that a specific error is thrown, only that an error has been thrown.


Lastly, there's a really nice library called tsd which helps you test your types. It's actually pretty similar to the vitest runner, but it bundles in everything and makes it nice to work with. I don't have as much experience with it, but it's definitely worth a look.


Those are three ways that you should be thinking about testing your types. It's worth noting that for application development, you're rarely going to be in a position where testing your types will be worth it.

But if you're working with any kind of library that is going to be consumed by a lot of people, then learning how to test types - and getting good at it - is critical.

Matt's signature

Share this article with your friends

"Sorry, I Need A TypeScript Playground In Order To Help"

Learn how to provide a TypeScript playground when asking for help with your TypeScript questions, making it easier for others to assist you.

Matt Pocock
Matt Pocock

Relative import paths need explicit file extensions in EcmaScript imports

When using '--moduleResolution' with the option 'nodenext', it is necessary to add explicit file extensions to relative import paths in EcmaScript imports.

Matt Pocock
Matt Pocock