TypeScript环境下使用Jest模拟模块内未导出内部函数的实现方法
如何模拟模块内未导出的函数进行单元测试?
最近写单元测试的时候卡壳了——我有个模块里的hello函数没导出,但被要测试的say调用了,现在想模拟hello却不知道怎么下手。先给大家看看我的代码:
file.ts
const hello = () => { console.log('hello'); } export const say = () =>{ hello() return 10; }
file.test.ts
import { say } from 'file'; describe("Test ", () => { it("test", async () => { // 如何在此处模拟hello函数? say(); }) })
下面给大家分享几种可行的解决方案:
方法1:用rewire库直接访问内部函数
rewire是个专门用来操作模块内部私有成员的工具,不管是CommonJS还是ES模块都能用。步骤很简单:
- 先安装依赖:
npm install --save-dev rewire
- 修改测试文件:
import rewire from 'rewire'; // 用rewire加载目标模块 const fileModule = rewire('./file'); import { say } from './file'; describe("Test ", () => { it("test", async () => { // 先保存原函数,测试完要恢复,避免影响其他用例 const originalHello = fileModule.__get__('hello'); // 把内部的hello替换成jest模拟函数 fileModule.__set__('hello', jest.fn()); say(); // 验证模拟函数确实被调用了 expect(fileModule.__get__('hello')).toHaveBeenCalled(); // 恢复原函数 fileModule.__set__('hello', originalHello); }) })
方法2:用Jest手动重写模块(仅适用于Jest环境)
如果你的项目用Jest做测试框架,可以直接通过jest.mock来手动构建模块,把内部的hello暴露出来并模拟:
import { say } from './file'; // 手动mock整个模块 jest.mock('./file', () => { // 先拿到原模块的所有内容 const originalModule = jest.requireActual('./file'); return { ...originalModule, __esModule: true, // 针对ES模块必须加这个标记 hello: jest.fn(), // 把内部的hello暴露出来并替换成模拟函数 }; }); // 从mock的模块里取出我们手动加的hello const { hello } = jest.requireMock('./file'); describe("Test ", () => { it("test", async () => { say(); // 验证模拟函数被调用 expect(hello).toHaveBeenCalled(); }) })
方法3:重构代码(推荐的长期方案)
其实如果经常需要测试内部私有函数,说明代码的耦合度有点高。更健康的做法是把hello拆到单独的模块,或者干脆导出它(如果只是测试用,可以加个注释说明,或者用// @ts-ignore忽略TypeScript的导出警告):
修改后的file.ts:
// 导出hello,仅用于测试 export const hello = () => { console.log('hello'); } export const say = () =>{ hello() return 10; }
这样测试起来就简单多了,直接用jest.spyOn或者jest.mock就行:
import { say, hello } from './file'; describe("Test ", () => { it("test", async () => { // 用spyOn模拟hello函数 const mockHello = jest.spyOn(require('./file'), 'hello').mockImplementation(() => {}); say(); expect(mockHello).toHaveBeenCalled(); // 测试完恢复原函数 mockHello.mockRestore(); }) })
这种方案不仅解决了测试问题,还让代码的职责更清晰,后续维护也更方便。
内容的提问来源于stack exchange,提问作者Roman Hrybinchuk
相关产品推荐
相关产品推荐

