在 TypeScript 中测试类型

这篇博客主要探讨了如何测试复杂的 TypeScript 类型是否按预期工作,需要在类型级别使用断言和其他工具。

  • 断言在类型级别:编写更复杂的类型如同在不同级别编程,在程序级别使用 JavaScript,在类型级别使用 TypeScript。在程序级别可使用assert.deepEqual()测试代码,在类型级别则需要类似的断言,如type Pair<T> = [T, T]; type Result = Pair<'abc'>; type _ = Assert<Equal<Result, ['abc', 'abc']>>;,这里的AssertEqual来自asserttt包。
  • 如何检查两个类型是否相等

    • 朴素解决方案:两个类型XY相等当且仅当X扩展YY扩展X,通过type SimpleEqual1<X, Y> = X extends Y? (Y extends X? true : false) : false;实现,但对于联合类型会出现问题。
    • 禁用分布:为使SimpleEqual按预期工作,需关闭分布,将extends两边变为单元素元组,如type SimpleEqual2<X, Y> = [X] extends [Y]? ([Y] extends [X]? true : false) : false;,可正确处理联合类型,但无法区分any
    • 严格比较any:通过一个技巧type StrictEqual<X, Y> = (<T>() => T extends X? 1 : 2) extends (<T>() => T extends Y? 1 : 2)? true : false;来区分any和其他类型。
  • 如何断言某事物必须为true:在程序/JavaScript 级别可通过抛出异常进行断言,在 TypeScript 中无法在编译时失败,但可通过type AssertType2<_B extends true> = void;这种 workaround 来实现,不过有其局限性,如type AssertEqual<X, Y> = true extends Equal<X, Y>? void : Fail;只能通过Fail实现某些功能。还可通过Not类型来断言一个类型不等于另一个类型,Not是一个谓词类型,asserttt还定义了其他谓词类型。
  • 断言错误:在 JavaScript 级别可使用assert.throws(),在类型级别可使用@ts-expect-error,默认它只检查错误是否存在,要检查具体错误可使用ts-expect-error包。
  • 断言值的类型:测试类型时可通过typeof运算符或assertType()函数来检查值的类型,assertType()函数可直接接受程序级别的值进行静态检查,但只能检查可赋值性,不能检查类型相等性,而Equal可用于检查值的类型是否不具有比给定类型更具体的类型。
  • 在普通代码中使用类型级断言的用例:在普通(非测试)代码中,类型级断言也很有用,如对于有类型Person和包含其键的数组personKeys,可使用类型级断言type _1 = Assert<Equal<keyof Person, (typeof personKeys)[number]>>;来让编译器在personKeys出错时发出警告。
  • 运行类型级测试:运行 TypeScript 编写的普通测试需运行编译后的 JavaScript,若测试包含类型级断言,还需额外进行类型检查,可先运行 JavaScript 再用tsc进行类型检查,或使用tsx等工具在运行代码前进行类型检查。
  • 进一步阅读:2ality 博客文章“Testing static types in TypeScript”提供了各种类型测试方法的概述。
阅读 8
0 条评论