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

Jest解构导入导致jest.requireActual失效、无法拦截原方法Mock的解决方法

Jest 单元测试 Mock 失效问题与解决方案

问题场景

需要为调用两个工具函数的controller编写单元测试,覆盖两个核心验证目标:

  • 验证子工具函数是否被controller正确调用
  • 验证controller返回的计算结果是否准确

项目涉及的代码如下:

  1. 工具函数实现 ./utilities/sayMyName.js
    exports.sayMyName = (name) => name;
    
  2. 工具函数实现 ./utilities/doubleNum.js
    exports.doubleNum = (num) => num * 2;
    
  3. 工具统一导出入口 ./utilities/index.js
    const { doubleNum } = require("./doubleNum");
    const { sayMyName } = require("./sayMyName");
    
    module.exports = {
      doubleNum,
      sayMyName,
    };
    
  4. 待测试的controller实现 myController.js
    const { doubleNum, sayMyName } = require("./utilities");
    
    exports.doubleMyNum = (num, name) => {
      const myName = sayMyName(name);
      return doubleNum(num);
    };
    
  5. 初版测试用例 myController.test.js
    const myController = require("./myController");
    const utilities = require("./utilities");
    
    jest.mock("./utilities");
    
    describe("doubleNum", () => {
      test("should call sayMyName", () => {
        myController.doubleMyNum(2, "test name");
        expect(utilities.sayMyName).toHaveBeenCalledWith("test name");
      });
    
      test("should double my number", () => {
        const { doubleNum } = jest.requireActual("./utilities");
        expect(myController.doubleMyNum(2, "test name")).toBe(4);
      });
    });
    

运行现象

  • 第一个验证子函数调用的测试用例可以正常通过
  • 第二个验证计算结果准确性的测试用例执行失败

问题根因

测试文件最开头通过jest.mock("./utilities")对整个工具模块做了全量Mock,按照Jest官方文档说明,jest.requireActual本应能获取模块的原始实现,但实际未生效。
核心原因:当模块被Mock后,myController.js已经在测试初始化阶段就加载完成,持有了Mock版本的工具函数引用。即使后续通过jest.requireActual获取了原始实现,已经加载完成的myController.js不会感知到模块导出的变更,仍会调用最初绑定的Mock函数。
叠加项目的写法特征,问题会更明显:

  • utilities/index.js 做了统一的导出映射,将独立工具函数重新组合后对外暴露
  • myController.js 采用解构导入的方式从utilities模块引入方法,在模块加载时就将函数引用绑定到了本地作用域

可运行的解决方案

核心处理逻辑:在需要使用原始实现的测试用例中,先通过jest.resetModules清空模块缓存,再重新导入myController.js,让controller能拿到当前最新的模块导出内容。
调整后的可运行测试代码:

const myController = require("./myController");
const utilities = require("./utilities");

jest.mock("./utilities");

describe("doubleNum", () => {
  beforeEach(() => {
    jest.resetModules();
  });

  test("should call sayMyName", () => {
    const spy = jest.spyOn(utilities, "sayMyName");
    myController.doubleMyNum(2, "test name");
    expect(spy).toHaveBeenCalledWith("test name");
  });

  test("should double my number", () => {
    jest.mock("./utilities", () => {
      const { sayMyName, doubleNum } = jest.requireActual("./utilities");

      return {
        sayMyName,
        doubleNum,
      };
    });

    // 重新导入myController,使utilities模块的原始实现生效
    const myController = require("./myController");

    expect(myController.doubleMyNum(2, "test name")).toBe(4);
  });
});

补充说明:如果myController.js没有使用解构导入的写法,而是直接在调用时从utilities模块上取方法,上述问题的处理流程还可以进一步简化。


内容的提问来源于stack exchange,提问作者John Marsden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.29 09:27:05