DEV Community

Cover image for How to test your typescript utility types
ecyrbe
ecyrbe

Posted on

How to test your typescript utility types

Typescript has some nice utility types 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 :

type Parameters<T extends (...args: any) => any> = 
     T extends (...args: infer P) => any ? P : never;
Enter fullscreen mode Exit fullscreen mode

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 :

type Equals<T,U> = T extends U ? U extends T ? true : false : false;
Enter fullscreen mode Exit fullscreen mode

This will work until you realise that this returns true :

type Test = Equals< 1, 1 | 2 >;
Enter fullscreen mode Exit fullscreen mode

This is due to how typescript conditional types work. It's doing distributive 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 :

type Equals<T,U> = [T] extends [U] ? [U] extends [T]? true: false : false;
Enter fullscreen mode Exit fullscreen mode

Now :

type Test = Equals< 1, 1 | 2 >;
Enter fullscreen mode Exit fullscreen mode

returns false.

But we are not there yet. Indeed, because of any, this will return true :

type Test = Equals< any, 1 | 2 >;
Enter fullscreen mode Exit fullscreen mode

Solution

type Equals<T, U> =
    (<V>() => V extends T ? 1 : 2) extends
    (<V>() => V extends U ? 1 : 2) ? true : false;
Enter fullscreen mode Exit fullscreen mode

This solution relies on internal understanding of Typescript isTypeIdenticalTo. For a more detailed explanation, you can check this issue

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 :

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 };
Enter fullscreen mode Exit fullscreen mode

Now when Assert checks that two types are not equal, you'll get a nice message.

And you can use it like this :

const test1: Assert<Parameters<(a: number)=>void>,[number]> = true; // pass
const test2: Assert<Parameters<(a: number)=>void>,[string]> = true; // fail
Enter fullscreen mode Exit fullscreen mode

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 for zodios library.

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

Top comments (0)