How to write tests for TypeScript types?
And when do we need them?

Search for a command to run...
And when do we need them?

No comments yet. Be the first to comment.
As a React developer, I never really questioned React's approach to reactivity. But I recently discovered two other frameworks: SolidJS and Svelte. Both frameworks have in common to not rely on a virtual DOM and to offer, what I call, “opt-in reactiv...
Today, I wanted to test the library: SolidJS. It features JSX support and is known for its great performance. Install the lib The first step I did was to follow the Getting Started guide and start from a given template: > npx degit solidjs/templates/...

And why would you do that?

In modern Front-End development, it is well known you should never use an HTML table to achieve your page layout. That doesn't mean you must not use the table value from the display CSS property. About display: table; The behavior of the display: ta...

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.
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 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. 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:
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 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.
Let's take the following custom type guard :
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 :
// 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.
This can be tested using ts-expect. This tool offers a "dumb" function and two generics that raise errors at compilation time if type constraints are not satisfied.
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
tscwith the--noEmitflag to prevent any output.
expect-type is also interesting because it features an API similar to jest.
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.
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:
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
.notword to do "negative assertions" :expectTypeOf(animal).not.toEqualTypeOf<Bird>();
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.