mcpField
T
test-your-types

test-your-types

github.com/marius-townhouseテスト・品質643 回インストール38 週間前に更新
リポジトリ

test-your-types について

TypeScript の型を実行時コードと同様にテストするためのスキルです。ライブラリー作者や複雑な型ユーティリティの開発者が、有効・無効なケースやエラー動作、実装とのずれを検証できます。

主な機能

  1. 01有効・無効な型のテスト
  2. 02想定されるコンパイルエラーの確認
  3. 03型ユーティリティと境界ケースのテスト
  4. 04tsd、expect-type、Vitest の利用
  5. 05型エラーメッセージの検証

活用シーン

  1. 01ライブラリーの型宣言のテスト
  2. 02複雑な型ユーティリティの検証
  3. 03型宣言と実装のずれの防止

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill test-your-types

インストール後、Claude Code で/test-your-typesを入力して読み込まれたか確認します。

詳細

型のテストを書く

概要

実行時コードのテストを書くのと同じように、型のテストも書くべきです。型レベルのコードにもバグがあり、型宣言が実装と一致しなくなることもあります。型をテストすることで、型宣言が正しく機能し、想定されるエラーを検出できるか確認できます。

型のテストは、ライブラリの作成者や複雑な型ユーティリティを扱う場合、また型を実装とは別に定義する場合に特に重要です。

このスキルを使うタイミング

  • ライブラリの型宣言を書くとき
  • 複雑な型ユーティリティを作成するとき
  • 型と実装が別のファイルにあるとき
  • 型レベルのコードをリファクタリングするとき
  • 型に条件分岐や再帰が含まれるとき

絶対に守るべきルール

型のテストを書きましょう。有効な型が機能すること、無効な型がエラーになること、エラーメッセージが役に立つことをテストしてください。

検出

次の状況に注意してください。

// RED FLAGS - Untested type logic
type ComplexTransform<T> = /* 10 lines of conditional types */;
// No tests - how do you know it works?

declare function libraryFn<T>(input: T): SomeTransform<T>;
// Implementation in JS, types in d.ts - can drift apart

テストする内容

次の3点をテストします。

  1. 有効な型が機能する - 期待される型が生成される
  2. 無効な型がエラーになる - 想定される箇所で型エラーが発生する
  3. エラーメッセージが役に立つ - エラーが修正方法を示してくれる

@ts-expect-error を使ったテスト

@ts-expect-error を使って、行で型エラーが発生することを検証します。

// myFunction.test.ts
import { myFunction } from './myFunction';

// Test 1: Valid types work
const result1 = myFunction('hello');
type Test1 = typeof result1;  // Should be string

// Test 2: Invalid types fail
// @ts-expect-error - number not assignable to string
const result2 = myFunction(42);

// Test 3: Error message is helpful
// When the error goes away, TypeScript warns:
// "Unused '@ts-expect-error' directive"

型ユーティリティのテスト

// type-utils.ts
export type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object 
    ? DeepReadonly<T[K]> 
    : T[K];
};

// type-utils.test.ts
import type { DeepReadonly } from './type-utils';

// Test: Simple object
type Case1 = DeepReadonly<{ x: number }>;
const test1: Case1 = { x: 1 };
// @ts-expect-error - readonly
test1.x = 2;

// Test: Nested object
type Case2 = DeepReadonly<{ nested: { value: string } }>;
const test2: Case2 = { nested: { value: 'hi' } };
// @ts-expect-error - deeply readonly
// @ts-expect-error - deeply readonly
test2.nested.value = 'bye';

// Test: Arrays
type Case3 = DeepReadonly<string[]>;
const test3: Case3 = ['a', 'b'];
// @ts-expect-error - readonly array
test3.push('c');

expect-type を使ったテスト

expect-type ライブラリには、型アサーション用のヘルパーが用意されています。

import { expectType, expectError } from 'expect-type';

// Test return types
const result = myFunction('input');
expectType<string>(result);

// Test that errors occur
expectError(myFunction(42));

// Test complex types
interface User { name: string; }
const user = fetchUser();
expectType<User>(user);

tsd を使ったテスト

tsd は、型定義をテストする CLI ツールです。

// index.test-d.ts
import { expectType, expectError } from 'tsd';
import { concat } from '.';

// Test: string + string = string
expectType<string>(concat('foo', 'bar'));

// Test: number + number = number  
expectType<number>(concat(1, 2));

// Test: mixed types = error
expectError(concat('foo', 1));

次のコマンドで実行します:npx tsd

Vitest を使ったテスト

Vitest には型テスト機能が組み込まれています。

// test/types.test-d.ts
import { describe, expectTypeOf, it } from 'vitest';
import { pick } from './utils';

describe('pick', () => {
  it('should pick specified keys', () => {
    const obj = { a: 1, b: 2, c: 3 };
    const picked = pick(obj, 'a', 'b');
    
    expectTypeOf(picked).toEqualTypeOf<{ a: number; b: number }>();
  });
  
  it('should not allow unpicked keys', () => {
    const obj = { a: 1, b: 2 };
    const picked = pick(obj, 'a');
    
    // @ts-expect-error - 'b' was not picked
    picked.b;
  });
});

次のコマンドで実行します:npx vitest typecheck

エラーメッセージのテスト

わかりやすいエラーメッセージは API の一部です。

// Test that error messages are helpful
type Check<T> = T extends string ? T : never;

// Bad: Error is just "Type 'number' is not assignable to type 'never'"
type Bad = Check<number>;

// Better: Use meaningful type names
type CheckWithMessage<T> = T extends string 
  ? T 
  : 'Error: Expected string, received something else';

エッジケースのテスト

// Test with unions
type UnionTest = MyType<string | number>;
// Should distribute: MyType<string> | MyType<number>

// Test with never
type NeverTest = MyType<never>;
// Should handle never gracefully

// Test with any
type AnyTest = MyType<any>;
// Should not crash or produce unexpected results

// Test with complex objects
type ComplexTest = MyType<{ a: { b: { c: string } } }>;
// Should handle nesting correctly

圧力に負けないためのプロトコル

型テストを省略するよう求められたときは、次のように対応します。

  1. リスクを示す:テストされていない型には、見つけにくいバグが潜む可能性があります
  2. 簡単なところから始める:@ts-expect-error を使ったテストは簡単に追加できます
  3. 自動化する:CI パイプラインに型テストを追加します
  4. 文書化する:テストは複雑な型のドキュメントとして役立ちます

注意すべき兆候

アンチパターン問題点
複雑なユーティリティに型テストがないバグが見過ごされます
正常系しかテストしていないエッジケースで問題が発生します
型とテストが別のファイルにある互いにずれていく可能性があります
IDE で手動テストしている再現できません

よくある正当化

「型チェッカーがエラーを検出してくれる」

実際には:型チェッカーは型に照らして検証しますが、その型が正しいかどうかは検証しません。それを検証できるのはテストだけです。

「ただの型であって、本物のコードではない」

実際には:型はコンパイル時に実行されるコードです。複雑な型のロジックにも、実行時のロジックと同じようにテストが必要です。

「何かが壊れたら気づくはずだ」

実際には:型のバグは見つけにくいものです。ユーザーから問題が報告されるまで気づかないこともあります。

クイックリファレンス

ツール最適な用途コマンド
@ts-expect-error手軽なテスト、インライン組み込み
expect-type単体テスト形式npm test
tsdライブラリの型定義npx tsd
Vitestテストスイート全体npx vitest typecheck
dtslintDefinitelyTypednpx dtslint

要点

型はコードであり、テストが必要です。手軽な確認には @ts-expect-error を使い、包括的なテストには専用ライブラリを使いましょう。有効なケース、無効なケース、エラーメッセージをテストしてください。

参考資料

  • Dan Vanderkam 著『Effective TypeScript』第2版
  • 項目55:型のテストを書く

コメント