You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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模块直接修改模块内部的函数引用,完全不需要改动生产代码:

  1. 先安装依赖:
npm install rewire --save-dev
  1. 测试代码:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.16 02:07:03