Testing static types in TypeScript

Testing static types in TypeScript

In this article, we’ll examine how we can test static types in TypeScript (inferred types, etc.)

Warning: This is experimental work. Read the comments for more information on its limitations.

For example, given the following function:

function createPoint(x: number, y: number) {
  return {x, y};
}

We’d like to check in a unit test that TypeScript infers this return type:

{x: number, y: number} 

In order to do that, we need a few tools that we are going to look at first.

1 - Conditional types

A conditional type lets you switch between two types, depending on whether a given type fulfills a type assertion:

«Type» extends «TypeAssertion» ? «ThenType» : «ElseType» 

In this case, “fulfilling” means: Is Type a subtype of TypeAssertion? If you think of types as sets then “subtype of” means “subset of”.

At the moment (due to limitations of TypeScript type inference), conditional types are mainly useful for computing with types. And not, e.g., to type the results of functions.

*1.1 - A simple example *

In the following example, we store the result of computing with types in a type Result:

type Result = RegExp extends Object ? true : false;
  // type Result = true;

Note that true and false are types here (so-called literal types).

1.2 - Parameterized types as functions for types

In the following example, we use the parameterized type YesNo like a function for types. T is a type parameter.

type YesNo<T extends boolean> = T extends true ? 'yes' : 'no';

type yes = YesNo<true>;
  // type yes = 'yes';
type no = YesNo<false>;
  // type no = 'no';

2 - Asserting types via conditional types

We can use conditional types to test static types. For example, via the following parameterized type:

type AssertIsString<S> = S extends string ? true : never; 

This type has a parameter S whose value is a static type. If that type is a subtype of string, the result of using AssertIsString is the type true. Otherwise, it is the type <a href="https://www.typescriptlang.org/docs/handbook/basic-types.html#never" target="_blank">never</a>. If a variable has that type then no real value can be assigned to it.

This is an example of using AssertIsString – we want to check that strValue does have the statically inferred type string (or a subtype):

const strValue = 'abc';
const cond1: AssertIsString<typeof strValue> = true;
  // No type error: the assertion is true

The next example checks if numberValue does have the type string and fails.

const numberValue = 123;
// @ts-ignore: Type 'true' is not assignable to type 'never'.
const cond2: AssertIsString<typeof numberValue> = true;

As an aside, the following simplification of this technique does not work:

type cond3 = AssertIsString<typeof numberValue>;
  // type cond3 = never;

The condition of AssertIsString goes to the else branch, but the resulting type never is not in conflict with anything and does not produce an error.

3 - Extracting return types

TypeScript provides several parameterized utility types. One of them is relevant for us here:

ReturnType<T> 

Its result is the return type of a function type. In the next example, we use it to assert that the return type of twice() is string:

function twice(x: string) {
  return x + x;
}
const cond: AssertIsString<ReturnType<typeof twice>> = true;

4 - Generic type tests

The following parameterized type builds on the ideas we have already seen and checks whether a given type T is equal to an expected type Expected:

type AssertEqual<T, Expected> =
  T extends Expected
  ? (Expected extends T ? true : never)
  : never;

We are checking two conditions:

  • Is T a subtype of Expected?
  • Is Expected a subtype of T?

If both are true then T is equal to Expected.

AssertEqual in action

The following example uses AssertEqual:

function createPoint(x: number, y: number) {
  return {x, y};
}

const cond1: AssertEqual<
    typeof createPoint,
    (x: number, y: number) => {x: number, y: number}
  > = true;

We can also just check the inferred return type:

const cond2: AssertEqual<
    ReturnType<typeof createPoint>,
    {x: number, y: number}
  > = true;

Angular 9 Tutorial: Learn to Build a CRUD Angular App Quickly

What's new in Bootstrap 5 and when Bootstrap 5 release date?

Brave, Chrome, Firefox, Opera or Edge: Which is Better and Faster?

How to Build Progressive Web Apps (PWA) using Angular 9

What is new features in Javascript ES2020 ECMAScript 2020

An Introduction to Unit Testing in Angular

Unit testing, as the name implies, is about testing individual units of code. Unit tests try to answer questions such as "Did I think about the logic correctly?" or "Does the sort function order the list in the right order

JavaScript Testing - Unit Tests, Integration Tests & e2e Tests

JavaScript testing - i.e. unit tests, integration tests and e2e (UI) tests - can be intimidating. It shouldn't be! This video guides you through all the basics (including the "Why"?) of JavaScript testing. JavaScript Testing Introduction Tutorial - Unit Tests, Integration Tests & e2e Tests. Master JavaScript testing now!

What TypeScript taught me about JavaScript

What TypeScript taught me about JavaScript. TypeScript was designed to make the most sense out of any JavaScript code. How void behaves in both TypeScript and JavaScript. What Symbols are and why they can be unique. Why substitutability is such an important concept for TypeScript