TypeScript中如何用函数复刻类的implements功能并兼容Jest Mock?
先明确你的场景:给定接口
interface Foo { a: () => string }
用类实现时,TypeScript既能保证类符合接口要求,又能识别a是jest.Mock类型,调用mockImplementation完全没问题——不过注意原代码里的类写法有个小错误,a应该用等号赋值,而非冒号(冒号是类型声明,不会给属性赋值):
class Bar implements Foo { a = jest.fn(() => 'hello'); } const bar = new Bar(); bar.a.mockImplementation(() => 'b') // 无类型错误
但用工厂函数实现时,返回的对象被接口Foo约束后,a的类型被收窄成() => string,导致无法调用Mock的专属方法:
function newFoo (): Foo { return { a: jest.fn(() => 'a') } } const foo = newFoo(); foo.a.mockImplementation(() => 'b') // 报错:mockImplementation不存在于()=>string类型
核心原因
类中直接给a赋值为jest.fn(),TypeScript会自动推断a的类型是jest.Mock<string, []>,而这个类型天然兼容Foo接口里的() => string,所以既能通过接口校验,又能保留Mock的类型信息。但工厂函数因为明确指定返回类型为Foo,TypeScript会强制把返回对象的类型收窄到接口定义,丢失了Mock的额外方法类型。
解决方案
方案1:临时类型断言(简单直接)
在调用Mock方法时,把foo.a断言为jest.Mock:
const foo = newFoo(); (foo.a as jest.Mock).mockImplementation(() => 'b')
优点是改动小,缺点是每次调用Mock方法都要写断言,略显繁琐。
方案2:返回交叉类型(兼顾约束与类型保留)
让工厂函数返回Foo与Mock类型的交叉类型,既保证符合接口要求,又能识别Mock的方法:
function newFoo(): Foo & { a: jest.Mock<string, []> } { return { a: jest.fn(() => 'a') } } const foo = newFoo(); foo.a.mockImplementation(() => 'b') // 无类型错误
方案3:用satisfies关键字自动推导(最优雅,TS 4.9+支持)
去掉函数的返回类型声明,让TypeScript自动推导返回对象的类型,同时用satisfies确保对象符合Foo接口:
function newFoo() { return { a: jest.fn(() => 'a') } satisfies Foo; } const foo = newFoo(); foo.a.mockImplementation(() => 'b') // 无类型错误
这个方案既保证了返回对象的接口合规性,又完整保留了Mock的类型信息,不用手动维护额外类型。
关于你的背景需求
你提到选择工厂函数是因为闭包封装性更好、类的私有属性会被Object.keys()识别、第三方库会对意外属性报错——以上方案都不会给对象添加可枚举的额外属性,Jest Mock的方法都是不可枚举的,完全符合你的需求。
内容的提问来源于stack exchange,提问作者Mike Davies

