3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Deno] TypeScriptの "型定義" をテストする方法(testing/typesモジュール)

Last updated at Posted at 2023-03-19

Denoの標準ライブラリにはTypeScriptの「型定義」をテストするためのtesting/typesモジュールがあります。この記事はそれの紹介です。

型定義のテストとは

Conditional Typeを使用して型の条件分岐を行っている時や、引数に指定する値によって戻り値の型が変わる関数の場合、「正しい型が返ってきているかどうか」をテストしたい場合があります。

例として以下のような関数を考えます。

function helloOrWorld<T extends string>(arg: T): T extends true ? "hello" : T extends false ? "world" : "hello" | "world" {
  // 実装は省略
}

helloOrWorld(true); // この戻り値の型が正しいかどうかチェックしたい

従来はhelloOrWorld(true)のところにマウスを当て、手動で正しい型が返ってきているかを確認していました。
これをテストコードとして書くことができ、CIでチェックできるようになるのが、今回紹介するtesting/typesモジュールです!

Deno以外の環境における型定義のテストについては、以下の記事も参考になります。

testing/typesモジュールの使い方

testing/typesモジュールの使い方を解説します。

testing/typesモジュールには、型定義のテストに使える以下のユーティリティが含まれています。

これらのユーティリティは型チェック時のみに意味があります。実行時には何もチェックしてくれません

重要度 関数名 使用例 説明
Assert<T, Expected> Assert<Has<...>, true> Tが型Expected(true又はfalse)と一致しなければ型エラー
★★★ AssertTrue<T> AssertTrue<IsExact<...>> Tがtrueと一致しなければ型エラー
AssertFalse<T> AssertFalse<Has<...>> Tがfalseと一致しなければ型エラー
Has<T, U> Has<100, typeof foo> Tが型Uに含まれていればtrue、そうでなければfalse
NotHas<T, U> NotHas<"hello", typeof foo> Tが型Uに含まれていなければtrue、そうでなければfalse
★★★ IsExact<T, U> IsExact<string|number, typeof foo> Tと型Uが正確に一致すればtrue、そうでなければfalse
IsAny<T> IsAny<typeof foo> Tがany型ならtrue、そうでなければfalse
IsNever<T> IsNever<typeof foo> Tがnever型ならtrue、そうでなければfalse
IsNullable<T> IsNullable<typeof foo> Tがnullかundefinedならtrue、そうでなければfalse
IsUnknown<T> IsUnknown<typeof foo> Tがunknown型ならtrue、そうでなければfalse

上記のユーティリティを使用してテストを書くことで、正しい型になっていれば型チェックが通り、間違った型であれば型チェックが失敗するようなコードを書くことができます。

テストを書くには、

  1. まず下7つのチェック用ユーティリティ(HasIsExactなど)を使用してtruefalseを返し
  2. それを上3つのアサーション用ユーティリティ(AssertTrueAssertFalseなど)にかけて期待した値でなければ型エラーを起こす

というやり方で書きます。

具体的には、以下のようなコードになります。

import type {
  AssertTrue,
  IsExact,
} from "https://deno.land/std@0.180.0/testing/types.ts";

Deno.test({
  name: "typing test",
  fn() {
    const val = mySuperFunction();
    type _ = AssertTrue<IsExact<typeof val, string>>; // この行で型チェックしている
    // typeof val が string型と正確に一致するかどうか(一致しなければ型エラーが起こる)
  },
});

上記の例ではmySuperFunctionという関数の返り値の型が、string型と正確に一致するかどうかを調べています。
まずtypeof valで変数の型を取り、IsExactで目的の型と一致するか調べます。IsExactはtrueまたはfalseを返すため、それをAssertTrueに渡すことによって、trueでない場合に型エラーを発生させています。

IsExactの代わりにHasIsNullableを使いたい時も、同様の手法で実現できます。

型エラーが起こる場合、エディタ上で赤波線が引かれ、エラー扱いになるはずです。
これをCIなどでコマンドラインからチェックしたい時には、deno testコマンドを使います

> deno test

なお、deno runコマンドでは--checkオプションを渡さないと型チェックが走らないため、型エラーを検出できません。CI等で型チェックをかけたい場合は、deno testコマンドを使いましょう。

まとめ

  • 型定義のテストにはtesting/typesモジュールを使う
  • 基本パターンは、
    1. typeofで変数の型を取る
    2. IsExtractHasで型が正しいかどうか調べる
    3. その結果がtrueかfalseかを、AssertTrueAssertFalseでチェックする(正しくない場合に型エラーを発生させる)
      …という流れ。

関数の戻り値の型がうっかり変わってしまっていないかをチェックするなど、特にライブラリを書くときに便利な機能だと感じました。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?