使用Sinon Mock含同名命名空间的FastGlob库函数时遇到问题如何解决?
解决方法
问题根源
fast-glob 采用了 TypeScript 中函数与同名命名空间合并的特性,最终 export = FastGlob 导出的本身就是可调用的函数实体,命名空间下的sync/stream等成员是直接挂载在这个函数对象上的属性,不存在名为FastGlob的子属性,所以你之前尝试stub(FastGlob, "FastGlob")的写法自然无法匹配。
同时 TypeScript 的类型系统对这种合并导出做了特殊处理,默认只能识别到命名空间的属性,无法直接识别该对象本身是可被 stub 的函数,需要用类型断言绕开检查。
正确 Mock 写法
方案1:使用 proxyquire 替换模块(无额外框架依赖)
这是最稳妥的方式,不会受 ES 模块导入只读的限制:
import sinon, { SinonStub } from 'sinon'; import proxyquire from 'proxyquire'; import { Pattern, Options } from 'fast-glob'; // 1. 创建目标重载的 stub const globStub: SinonStub<[source: Pattern | Pattern[], options?: Options], Promise<string[]>> = sinon.stub(); // 模拟返回你需要的结果 globStub.resolves(['/test/file1.ts', '/test/file2.ts']); // 2. 加载业务代码时替换 fast-glob 依赖 const yourBusinessModule = proxyquire('./你的业务代码文件路径', { 'fast-glob': globStub }); // 3. 后续正常执行测试逻辑即可
如果需要保留命名空间下的其他方法(比如sync)不被 mock,只要把原模块的静态属性复制到 stub 上即可:
const originalFastGlob = require('fast-glob'); Object.assign(globStub, originalFastGlob);
方案2:直接类型断言绕开检查
如果不想引入 proxyquire,也可以通过类型断言强制修改导入对象:
import * as FastGlob from 'fast-glob'; import sinon, { SinonStub } from 'sinon'; import { Pattern, Options, Entry } from 'fast-glob'; // 断言 FastGlob 为可修改的函数对象,stub 其调用 const globStub = sinon.stub( FastGlob as unknown as { (source: Pattern | Pattern[], options?: Options): Promise<string[] | Entry[]> }, ).resolves(['/test/file1.ts']); // 测试调用 const res = await FastGlob('./my/glob/**/*.ts', { absolute: true }); console.log(res); // 输出模拟的 ['/test/file1.ts']
重载匹配说明
如果需要模拟不同的重载返回,只要给 stub 加条件判断即可,比如匹配objectMode为true的参数时返回 Entry 数组,否则返回字符串数组:
globStub.callsFake(async (source, options) => { if (options?.objectMode) { return [{ path: '/test/file1.ts' } as unknown as Entry]; } return ['/test/file1.ts']; });
内容的提问来源于stack exchange,提问作者myol
相关产品推荐
相关产品推荐

