How to test your typescript utility types
Tags: typescript, testing, tooling, tutorial
Published: 2022-06-19 20:08:14


Typescript has some nice [utility types](https://www.typescriptlang.org/docs/handbook/utility-types.html) to help you improve code typing.  
As a reminder, utility types, are like functions but for types. What this means is that utility types take `types` as parameters and return `types` as output.  
For example the utility named `Parameters<T>` is defined like this :
```typescript
type Parameters<T extends (...args: any) => any> = 
     T extends (...args: infer P) => any ? P : never;
```
It takes any function type as a parameter and returns a `Tuple` of all the function parameters.  
Sometimes you need to create your own custom utility types.  
And if usually you add some unit tests for your functions, you might also want to do the same for your utility type.  
  
But how to test them ? Does this mean that tests are run at compile time and fail to report errors at runtime ? can you do these tests with your favorite test Runner ?

## Utility to compare two types

First of all, if you want to test your utility types, at the minimum we will need to compare the output of your utility type to the expected one.

### First try

One could try to do [this](https://www.typescriptlang.org/play?ssl=4&ssc=1&pln=1&pc=1#code/C4TwDgpgBAogjgVwIYBsA8AVANAVQHxQC8UGUEAHsBAHYAmAzlDlAPxTABOC0AXFAGap6EANwAoMaEgkI9YEViJUaAN5IeABgC+WFVrzigA) :
```typescript
type Equals<T,U> = T extends U ? U extends T ? true : false : false;
```
This will work until you realise that this returns `true` :
```typescript
type Test = Equals< 1, 1 | 2 >;
```

This is due to how typescript conditional types work. It's doing [distributive](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#distributive-conditional-types) conditional typing.

Since we want to do strict type equality, we want to disable distributive behaviour.  

### Disabling distributive conditional typing

One way of doing it is to hide the type you want to test inside. For example, like [this](https://www.typescriptlang.org/play?ssl=4&ssc=1&pln=1&pc=1#code/C4TwDgpgBAogjgVwIYBsDOAeAKgGgKoB8UAvFANpYC6UEAHsBAHYAma5e1A-O9XQy2wqVuwAE4IIALigAzVGmjS56CAG4AUOtCQoWCGmAlYieRiSMQOKAEYAPgCYCGoA) :
```typescript
type Equals<T,U> = [T] extends [U] ? [U] extends [T]? true: false : false;
```
Now :
```typescript
type Test = Equals< 1, 1 | 2 >;
```
returns `false`.

But we are not there yet. Indeed, because of any, this will return true :
```typescript
type Test = Equals< any, 1 | 2 >;
```
### Solution

```typescript
type Equals<T, U> =
    (<V>() => V extends T ? 1 : 2) extends
    (<V>() => V extends U ? 1 : 2) ? true : false;
```
  
This solution relies on internal understanding of Typescript `isTypeIdenticalTo`. For a more detailed explanation, you can check this  [issue](https://github.com/microsoft/TypeScript/issues/27024#issuecomment-510924206)

## How to use it to run your utility type tests

When writing tests, you need to have something to help you diagnose the issue, a readable message.  
So you can write the previous test like this :
```typescript
type Assert<T, U> =
    (<V>() => V extends T ? 1 : 2) extends
    (<V>() => V extends U ? 1 : 2) ? true : 
    { error: "Types are not equal"; type1: T; type2: U };
```
Now when `Assert` checks that two types are not equal, you'll get a nice message.  
And you can use it like [this](https://www.typescriptlang.org/play?#code/C4TwDgpgBAggznCAnYAeAKgGigVQHxQC8AUFGVABSoBqeFAlEQdVBAB7AQB2AJnFOigB+KAEYoALigAmRu0684pclVoMmUFvO59cwsZJmMRwJAFdoU5WQDerJEgD2SKQCJ04CPwCGSaF0dgVgBHM28AG1cAbihQSFEpdBi4iGkpHCgAXyjiYgBjRy44IM5ihNgEZDQABV9vAFsITiQ4VApvKS4zeoAjZHpCPAA3RwBLHjxMAG0u3uQAXQJCWPMIGIB6dagwbwR8wuLYr2A0isQUVFqkBqbkVvbO7r6kAeGxienipFGuAHNFogrCwbLYAM28o3CxCAA) :
```typescript
const test1: Assert<Parameters<(a: number)=>void>,[number]> = true; // pass
const test2: Assert<Parameters<(a: number)=>void>,[string]> = true; // fail
```

## Conclusion

By using this `Assert` utility type (you can even use it to test itself) you'll be able to add unit tests for your own utility types.  
This will show you a nice message as to why it fails and where. It will allow you to monitor typescript regressions in the type system for your utility types.

if you want to see it in practice, you can check some [tests here](https://github.com/ecyrbe/zodios/blob/main/src/utils.types.test.ts) for [zodios](https://github.com/ecyrbe/zodios) library.

If this article was helpfull to you, don't forget to give it a thumbsup.