To make the construction and maintenance of more advanced types easier it can be helpful to write some tests that ensure their correct function. This sounds a little easier than it turns out to be.

As part of the ecosystem for TypeScript Microsoft have written and released the dtslint tool. It can be used to link and compile TypeScript types for static analysis and mostly serves to keep the @types/* packages in line.

Firstly, install the dependencies that we will need to test the types.

npm install --save-dev dtslint conditional-type-checks

Then in the directory you wish to write your tests (the examples in this article use a directory ./typings/__tests__ for this) - create a new file index.d.ts with the following contents:

// TypeScript Version: 3.3
// see https://github.com/Microsoft/dtslint#specify-a-typescript-version for more information

The first line lets dtslint know which TypeScript version you would like to test your types against.

In that same directory you will also need to include a tsconfig.json file like the following:

// this additional tsconfig is required by dtslint
// see: https://github.com/Microsoft/dtslint#typestsconfigjson
{
  "compilerOptions": {
    "module": "commonjs",
    "lib": ["es6"],
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true,
    "noEmit": true,

    // If the library is an external module (uses `export`), this allows your test file to import "mylib" instead of "./index".
    // If the library is global (cannot be imported via `import` or `require`), leave this out.
    "baseUrl": "."
  }
}

Finally, dtslint needs to be added to the configuration for tslint in the tslint.json:

{
  "extends": ["dtslint/dtslint.json"],
  "rules": {
    "no-useless-files": false,
    "no-relative-import-in-test": false
  }
}

You should now have a directory structure that looks something like the following:

typings
  `-- __tests__
    `-- index.d.ts
    `-- tsconfig.json
    `-- tslint.json

Now in your package.json file you can add a script to run the dtslint testing.

  "ts:dtslint": "dtslint ./typings/__tests__",

To make the next few steps easier to follow we’ll quickly write out a new TypeScript type in ./typings - without this we wouldn’t actually have anything to test! So, let’s write an implementation of the Omit type that now comes with the TypeScript standard library.

It uses both Pick and Exclude, which are also included with TypeScript. If they are new to you then you might like to read my previous article Advanced TypeScript Types to get an introduction first.

/**
 * Remove all keys listed in K from the object T
 *
 * @example type MyType = Omit<{ a: '1'; b: '2'; c: '3' }, 'a' | 'b'>  // { c: '3' }
 */
export type Omit<T extends object, K extends keyof T> = Pick<
  T,
  Exclude<keyof T, K>
>;

Now you are ready to write some tests for the types you have defined. dtslint uses the $ExpectType annotation to state the type of the type expression on the next line.

// $ExpectType Pick<{ a: "1"; b: "2"; c: "3"; }, "c">
type Test_01_Omit = Omit<{ a: "1"; b: "2"; c: "3" }, "a" | "b">;

dtslint will now evaluate the Test_01_Omit expression and determine the resultant type to compare it against the type you’ve specified with $ExpectType. If you’re anticipating your type to result in a type error then this can be asserted with $ExpectError. These are documented in the README for the dtslint project.

Next up we can use some of the assertion types from the conditional-type-checks package we installed earlier to run some additional unit style tests of the type.

type Test_02_Omit =
  | AssertTrue<IsExact<Test_01_Omit, { c: "3" }>>
  | AssertFalse<Has<Test_01_Omit, { a: "1"; b: "2" }>>;

Here the assertions state that the final evaluated expression only has one key c and does not have the keys a or b. There are more conditional types that you can employ documented in the project README.

Using both of these projects you can test the more advanced types that your projects employ to ensure their continued success against various TypeScript versions.