如何为 TS 类型写单测呢?
最简单的办法就是试探性访问属性,如果该属性访问不到自然会在异常时出现错误,如:
import { myLib } from "code";
myLib.update; // 正确
如上所示,如果 myLib 没有正确的开放 update 属性将会提示错误。但这种单测并不是我们要讲的类型。想一想,如果我们只开放 .update API 给用户,但框架内部可以使用全量的 .update、.add、.remove 方法,如何验证框架没有把不必要的属性也开放给了用户呢?
一种做法是直接访问类型提示,此时会出现错误下划线:
myLib.add
~~~ // Property 'add' does not exist on type MyLib
此时说明代码逻辑正常,但却抛出了 ts 错误,这可能会阻塞 CI 流程,而且我们也无从判断这个报错是否 “实际山是逻辑正确的表现”,所以 “不能出现某个属性” 就不能以直接访问属性的方式实现了,我们要做一些曲线方案。
我们可以利用 extends 构造三元类型表达式,逻辑是如果 myLib 拥有 .add 属性就返回 a 类型,否则返回 b 类型。因为 myLib 不该提供 .add 属性,所以下一步判断该新类型一定符合 b 即可:
const check: typeof myLib extends { add: any } ? number : number[] = [];
check.length; // 该行在没有 .add 属性时不会报错,反之则报错
因为我们给的默认值是字符串,而预期正确的结果也是进入 number[] 类型分支,所以 check.length 正常,如果某次改动误将 .add 提供了出来,check.length 就会报错,因为我们给值 [] 定义了 number 类型,访问 .length 属性肯定会出错。
另一种简化的办法是利用 true or false 判断变量类型是否匹配,如:
const check: typeof fn extends (a: any) => any ? true : false = true;
如果 fn 满足 (a: any) => any 类型,则 check 的类型限定为 true,否则为 false,所以当 fn 满足条件时该表达式正确,当 fn 不满足条件式,我们将变量 true 赋值给类型 false 的对象,会出现报错。
也许你会有疑问,可以将 ts 类型校验错误转换为 js 对象吗?这样就可以用 expect 等断言结合到测试框架流程中了。很可惜,至少现在是不行的,只能做到利用 js 变量推导类型,不能利用类型生成变量。
总结一下,如果想判断某些类型定义未暴露给用户,而实际上在 js 变量里是拥有这些属性的,就只能用类型方案判断正确性了。
比如变量 myLib 实际上拥有 .update 与 .add 方法,但提供给用户的类型定义刻意将 .add 隐藏了,此时校验方式是,利用一个跳板变量 check,使用 extends 判断其是否包含 add 属性,再利用特殊类型方法或者直接用赋值语句判断 extends 是否成立。
讨论地址是:精读《如何为 TS 类型写单测》· Issue #446 · dt-fe/weekly
如果你想参与讨论,请 点击这里,每周都有新的主题,周末或周一发布。前端精读 - 帮你筛选靠谱的内容。
版权声明:自由转载-非商用-非衍生-保持署名(创意共享 3.0 许可证)