Tkr Blog

TypeScriptで型のユニットテストをしたい

2018/09/10 typescript typelevelprogramming

やり方

準備

test.ts

export type TypeEq<A, B> = (<T>() => T extends A ? 1 : 2) extends (<T>() => T extends B ? 1 : 2) ? true : false;

export function assertType<_T extends true>() { }
export function assertNotType<_T extends false>() { }

色々複雑になっていますが、これはanyやunion、neverなどに対応するためです。 ここらへんの型は少し特殊なため、このようにしないと正常にテスト出来ません。

テストの書き方

assertType<TypeEq<1, 1>>();
assertNotType<TypeEq<1, 2>>();

テストが正しくなければコンパイルエラーが発生します。

import { TypeEq, assertType, assertNotType } from "./test";

assertType<TypeEq<1, 1>>();
assertNotType<TypeEq<{}, { x: 1 }>>();
assertNotType<TypeEq<1, 2>>();
assertNotType<TypeEq<1 | 2, 1>>();
assertNotType<TypeEq<1, never>>();
assertType<TypeEq<never, never>>();
assertNotType<TypeEq<1, any>>();
assertType<TypeEq<any, any>>();
assertNotType<TypeEq<1, unknown>>();
assertType<TypeEq<unknown, unknown>>();

そもそも何でこんなものが必要なの?

通常のTSの使い方なら必要ありません。 しかし、ts2.1でMapped typesが入り、ts2.8でConditional Typesが入り、ts3.0でTuple関連の型システムが強化されTSの型システムはより複雑に、そして高機能になっています。 このような型システムをフル活用すると例えばタプル型をReverseZipするといったとても複雑な事を行えます。[参考](例えばZip<[1,2,3],[10,20]>は型[[1,10],[2,20]]を返すなど) つまりtypeをただの型エイリアスではなく「型を受け取り型を返す関数」として使うことができます。 当然中身はかなり複雑ですし、バグも発生します。 関数のようなものなので正しい値を返してくれるかのテストを行いたいのです。

例えばさっきの例なら

assertType<TypeEq<Zip<[1,2,3],[10,20]>, [[1,10],[2,20]]>>();

のように書くことでテストを行えます。


kgtkr
Web Developer.