# How to write tests for TypeScript types?

Writing test is, essentially, making assertions. Most of the time, these assertions aim to check the runtime logic we implemented. But in some situations, we implement our own build-time logic: our own types. This article covers different use cases and tools we can use to write safer types.

## Testing declarations files

Declarations files must be written manually for JavaScript modules with no types for them to be used in a TypeScript project.

Writing declarations is a tedious task and mistakes can be made. The [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped) project (which provides many TypeScript declarations under the `@types` alias on npm) recommends writing tests to prevent these mistakes.

Declarations files tests in DefinitelyTyped rely on the project's internal tool: [dtslint](https://www.npmjs.com/package/dtslint). Tests are a bit different from what we are used to in a test framework like Jest. They are like regular TypeScript files, but annotated with comments that do the actual assertion:

```typescript
import { f } from "my-lib"; // f(n: number) => void

// $ExpectType void
f(1);

// $ExpectError
f("one");
```

**dtslint** provides an environment that compares the declarations files with those tests.

For declarations files located on **your project**, DefinitelyTyped advises using the lib [tsd](https://www.npmjs.com/package/tsd) instead. Both **dtslint** and **tsd** are meant to test declarations files only. In the next sections, we are going to explore other tools for other use cases.

## Testing advanced types

Let's take the following custom type guard :

```ts
function isFish(animal: Fish | Bird): pet is Fish {
  return (animal as Fish).swim !== undefined;
}
```

Like any other function, we would test its logic like so :

```typescript
// Given
const someFish = new Fish();
// Then
expect(isFish(someFish)).toBe(true);
```

But in addition to runtime logic, custom type guards also bring **build-time logic** to the table: the type predicate. When used in a condition, this function can narrow its input to the type suggested after the keyword `is`.

### Using ts-expect

This can be tested using [ts-expect](https://www.npmjs.com/package/ts-expect). This tool offers a "dumb" function and two generics that raise errors at compilation time if type constraints are not satisfied.

```typescript
import { expectType } from "ts-expect";

if(isFish(animal)) {
    expectType<Fish>(animal);
}
```

In this test, we check that `animal` is properly narrowed to Fish. If it compiles, that means the test is valid. Because this helper relies on a basic type inference from TypeScript, errors are easy to understand.

> Because this logic is not meant to be executed, I would recommend writing it in dedicated test files. You would compile them using `tsc` with the `--noEmit` flag to prevent any output.

### Using expect-type

[expect-type](https://www.npmjs.com/package/expect-type) is also interesting because it features an API similar to jest.

```typescript
import { expectTypeOf } from 'expect-type';

if(isFish(animal)) {
    expectTypeOf(animal).toEqualTypeOf<Fish>();
}
```

This library can be confusing because you can either pass an object as an argument to the function or pass a type using the generic of the function. Errors can also be a bit confusing compared to **ts-expect**.

### Assertions that should not be satisfied

It can be useful to test the opposite assertion: to expect that a type **does not** satisfy another. Typescript, since version 3.9, provides a very powerful directive:

```typescript
if(isFish(animal)) {
    expectType<Fish>(animal);
    // @ts-expect-error
    expectType<Bird>(animal);
}
```

If for any reason the line following the directive doesn't raise an error, the comment itself will raise the error: `Unused '@ts-expect-error' directive. ts(2578)` . This can prevent your code to be "poisoned" with `any` declarations.

> While this directive can be used with both libraries, **expect-type** provides the `.not` word to do "negative assertions" :
> 
> ```typescript
> expectTypeOf(animal).not.toEqualTypeOf<Bird>();
> ```

## tl;dr

You can use **tsd** to test your declarations files. If you want to test advanced types like custom type guards or bespoke generics, use can use **ts-expect** or **expect-type**. The `@ts-expect-error` directive can also be used to make sure your types are strict enough.
