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

Node.js中使用Jest模拟同文件内部函数有无更优方案?

在Node.js中用Jest模拟同文件内函数的最优方案

问题场景

原始业务代码(password.js)如下:

// password.js
async function main(){
  password = await getPassword();
  return password;
}

async function getPassword() {
  return 'thisIsMyPassword';
}

module.exports = {
  main,
  getPassword
}

当前通过修改main函数,让其调用module.exports.getPassword()实现了模拟,修改后的业务代码:

// password.js修改后
async function main(){
  password = await module.exports.getPassword();
  return password;
}

async function getPassword() {
  return 'thisIsMyPassword';
}

module.exports = {
  main,
  getPassword
}

配套测试代码:

const password = require('./password')
describe('Password test', () => {
    afterEach(() => {
         jest.restoreAllMocks();
    });

    test('return password', async () => {
        jest.clearAllMocks();
        jest.spyOn(password, 'getPassword').mockReturnValueOnce('ilovedualipa')

        expect(await password.main()).toEqual('ilovedualipa');
    });
});

疑问:不确定在业务代码中直接调用module.exports是否为最佳实践,希望找到更优的实现方式。

更优实现方案

方案1:将函数挂载到统一对象

把所有函数定义在一个对象内,内部调用通过this指向对象方法,既不破坏业务代码的可读性,又方便Jest进行mock。

修改后的业务代码:

// password.js
const passwordUtils = {
  async main() {
    const password = await this.getPassword();
    return password;
  },
  async getPassword() {
    return 'thisIsMyPassword';
  }
};

module.exports = passwordUtils;

测试代码无需改动,jest.spyOn作用于导出的对象后,main里的this.getPassword会自动指向被mock后的方法。

方案2:测试文件内重导出mock

如果完全不想修改业务代码,可以在测试中通过重导出的方式覆盖原始函数,利用Node.js的模块缓存机制实现mock。

业务代码保持原始版本不变,修改测试文件:

const * as passwordModule from './password';
jest.mock('./password', () => {
  // 获取原始模块的真实实现
  const originalModule = jest.requireActual('./password');
  return {
    ...originalModule,
    // 将getPassword替换为mock函数,默认执行原始逻辑
    getPassword: jest.fn(originalModule.getPassword)
  };
});

describe('Password test', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });

  test('return password', async () => {
    passwordModule.getPassword.mockReturnValueOnce('ilovedualipa');
    expect(await passwordModule.main()).toEqual('ilovedualipa');
  });
});

方案3:拆分模块(推荐)

将getPassword这类独立职责的函数拆分到单独模块,既符合模块化设计原则,也从根源上简化测试mock逻辑。

拆分后的文件结构:

// password-utils.js
async function getPassword() {
  return 'thisIsMyPassword';
}

module.exports = { getPassword };
// password.js
const { getPassword } = require('./password-utils');

async function main(){
  const password = await getPassword();
  return password;
}

module.exports = { main };

测试文件:

const { main } = require('./password');
const { getPassword } = require('./password-utils');

jest.mock('./password-utils');

describe('Password test', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });

  test('return password', async () => {
    getPassword.mockReturnValueOnce('ilovedualipa');
    expect(await main()).toEqual('ilovedualipa');
  });
});

总结

直接在业务代码中调用module.exports.getPassword()虽然可行,但会让业务代码耦合测试逻辑,不够优雅。优先推荐方案3(拆分模块),既符合代码设计规范,又让测试逻辑更清晰;如果无法拆分模块,**方案1(挂载到对象)**是最简洁的选择;**方案2(重导出mock)**适合完全不能修改业务代码的场景。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 17:23:24