TestCafe+TS使用类型化Context时报属性缺失类型错误
TypeScript 结合 TestCafe 自定义ctx类型报错问题
问题场景
我使用TypeScript已有一段时间,但偶尔仍会遇到难以排查的疑难问题。
参考Github issue中的代码,向任意测试钩子传入使用类型化上下文的函数时,会抛出如下错误:
Property 'color' is missing in type '{ [key: string]: any; }' but required in type 'MyContext'
触发该问题的代码如下:
interface MyContext { color: string } interface TestControllerEx<TContext> extends Omit<TestController, 'ctx'> { ctx: TContext; } fixture`Fixture1`.beforeEach(async (t: TestControllerEx<MyContext>) => { t.ctx.color = 'red' // strongly typed! }); test('Test1', async (t: TestControllerEx<MyContext>) => { await t.expect(t.ctx.color).eql('red'); // 'red' strongly typed! });
目前我通过将MyContext中的color属性设置为可选属性临时规避该问题,但我希望明确问题出现的根本原因:TestCafe的TestController接口将ctx属性类型定义为{ [key: string]: any },按理传入TestControllerEx的泛型类型本应满足该约束,我甚至尝试让TContext直接继承{ [key: string]: any }索引签名,仍然无法解决问题。
请问该问题是TestCafe的类型定义存在缺陷,还是由我尚未掌握的TypeScript特有类型机制导致的?
问题根因
这个问题是TestCafe类型定义的设计限制,叠加TypeScript函数参数的逆变检查规则共同导致的,和TS基础约束的理解偏差无关。
- TestCafe中
beforeEach、test这类API的类型签名,早就写死了回调函数的参数t是原生TestController类型。按照TS的类型检查规则,给这类API传回调时,回调的参数类型必须满足:原生传进来的TestController值能合法赋值给你标注的参数类型。 - TS对函数参数做的是逆变检查:简单说就是你标注的参数类型不能比API原生给的类型更严格,否则原生传进来的值满足不了你收窄后的要求,就会报错。
- 你写的
TestControllerEx<MyContext>把ctx的类型收窄到必须带必填color属性,但原生TestController的ctx默认是{[key: string]: any},这个类型本身不保证存在color字段,自然没法赋值给要求必须有color的MyContext,所以TS直接抛错。 - 之前给TContext加索引签名仍然报错的原因也很简单:索引签名只约束了任意字符串键的返回类型,只要
color是必填项,原生ctx对象上没这个属性,就过不了检查。
解决方案
不需要自己重写TestController的类型,直接用TestCafe原生支持的泛型参数声明上下文类型即可,这是最稳妥的写法,不会丢失类型提示:
interface MyContext { color: string } // 给fixture传入泛型类型,不需要自定义TestControllerEx fixture<MyContext>`Fixture1`.beforeEach(async (t) => { t.ctx.color = 'red' // t.ctx自动推导为MyContext类型,有完整类型校验 }); // test调用时也传入对应泛型即可 test<MyContext>('Test1', async (t) => { await t.expect(t.ctx.color).eql('red'); });
如果你用的TestCafe版本比较旧,不支持上述泛型写法,可以在回调内部做类型断言,比把属性改成可选的临时方案更安全,不会漏掉必填属性的校验:
fixture`Fixture1`.beforeEach(async (t) => { const typedT = t as TestControllerEx<MyContext> typedT.ctx.color = 'red' });
内容的提问来源于stack exchange,提问作者crcollver
相关产品推荐
相关产品推荐

