CommonJS模块测试时内部函数部分Mock失效问题求助
Jest同模块内函数Mock失效的原因与无侵入解决方案
问题场景
我有一个CommonJS格式的工具模块,里面的函数互相调用,代码如下:
function someFunctionToMock1(a, b) { console.log('Some long fetch function 1 which result needs to be mocked'); if (b) throw new Error('Just checking if the someFunctionToMock1 is mocked'); return 'results from someFunctionToMock1'; } function someFunctionToMock2(a, b) { console.log('Some long fetch function 2 which result needs to be mocked'); if (b) throw new Error('Just checking if the someFunctionToMock2 is mocked'); return 'results from someFunctionToMock2'; } function functionToTest(a, b) { console.log('My function is starting'); const resultsFromFunctionToMock1 = someFunctionToMock1(a, b); const resultsFromFunctionToMock2 = someFunctionToMock2(a, b); if (a && !b) return a; if (!a && b) return b; return a * b; } module.exports = { someFunctionToMock1, someFunctionToMock2, functionToTest };
我想用Jest测试functionToTest,同时Mock另外两个函数,写了测试代码但Mock完全没生效,反而触发了原函数里的错误:Error('Just checking if the someFunctionToMock1 is mocked')。拆分函数到单独模块就能正常Mock,但我不想改生产代码,这问题到底出在哪?
问题根源
核心原因是函数引用不匹配:functionToTest内部直接调用的是模块本地作用域里定义的someFunctionToMock1/someFunctionToMock2,而你用jest.mock替换的是module.exports导出的函数版本。这两个是完全独立的引用——原函数的调用逻辑根本没用到你Mock的导出对象,所以Mock自然不会生效。
无侵入解决方案(无需修改生产代码)
方案1:Mock时重写functionToTest,让它调用Mock后的函数
这种方法需要在测试里同步functionToTest的逻辑,但能保证Mock生效:
const myModule = require('.'); jest.mock('.', () => { const mock1 = jest.fn(() => 'mocked results 1'); const mock2 = jest.fn(() => 'mocked results 2'); return { someFunctionToMock1: mock1, someFunctionToMock2: mock2, // 重写functionToTest,调用Mock后的函数 functionToTest(a, b) { console.log('My function is starting'); const resultsFromFunctionToMock1 = mock1(a, b); const resultsFromFunctionToMock2 = mock2(a, b); if (a && !b) return a; if (!a && b) return b; return a * b; } }; }); test('should do a partial mock', () => { const results = myModule.functionToTest(2, 3); expect(results).toBe(6); expect(myModule.someFunctionToMock1).toHaveBeenCalledWith(2, 3); });
方案2:用jest.spyOn动态替换functionToTest的实现
和方案1类似,但更灵活,适合需要临时替换的场景:
const myModule = require('.'); // Mock导出的两个函数 const mockSpy1 = jest.spyOn(myModule, 'someFunctionToMock1').mockReturnValue('mocked results 1'); const mockSpy2 = jest.spyOn(myModule, 'someFunctionToMock2').mockReturnValue('mocked results 2'); // 重新定义functionToTest,让它调用Mock后的导出函数 myModule.functionToTest = (a, b) => { console.log('My function is starting'); const resultsFromFunctionToMock1 = myModule.someFunctionToMock1(a, b); const resultsFromFunctionToMock2 = myModule.someFunctionToMock2(a, b); if (a && !b) return a; if (!a && b) return b; return a * b; }; test('should do a partial mock', () => { const results = myModule.functionToTest(2, 3); expect(results).toBe(6); expect(mockSpy1).toHaveBeenCalledTimes(1); }); // 测试完成后恢复原函数 afterAll(() => { mockSpy1.mockRestore(); mockSpy2.mockRestore(); });
方案3:用rewire直接修改模块内部函数引用(最优雅)
如果不想同步functionToTest的逻辑,推荐用rewire模块直接修改模块内部的函数引用,完全不需要改动生产代码:
- 先安装依赖:
npm install rewire --save-dev
- 测试代码:
const rewire = require('rewire'); const myModule = rewire('.'); // 定义Mock函数 const mock1 = jest.fn(() => 'mocked results 1'); const mock2 = jest.fn(() => 'mocked results 2'); // 替换模块内部的函数引用 myModule.__set__('someFunctionToMock1', mock1); myModule.__set__('someFunctionToMock2', mock2); test('should do a partial mock', () => { const results = myModule.functionToTest(2, 3); expect(results).toBe(6); expect(mock1).toHaveBeenCalledWith(2, 3); });
内容的提问来源于stack exchange,提问作者kapalkat
相关产品推荐
相关产品推荐

