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
相关产品推荐
相关产品推荐

