如何在Jest中模拟Node.js的非全局变量__filename和__dirname?
在Jest中模拟__filename和__dirname的可行策略
__filename和__dirname是Node.js通过模块包装函数注入的特殊变量,不属于global对象,因此直接通过globalThis/global赋值的方式无法在Jest中模拟它们。以下是几种可行的策略,以及是否需要接受限制的分析:
一、理想方案:使用jest.mock重写模块(无业务代码侵入)
通过手动模拟目标模块的导出,直接构造包含自定义路径信息的返回值,完全在测试层实现隔离,不需要修改业务代码。
示例测试代码(utils.test.ts):
// 保留模块其他导出,仅重写目标函数 jest.mock('./utils', () => { const originalModule = jest.requireActual('./utils'); return { ...originalModule, getFileAndDirName: jest.fn(() => ({ relevantFile: 'utils', rootPath: '/Users/me/work/utils' })) }; }); import { getFileAndDirName } from './utils'; it('返回正确的文件和目录信息', () => { expect(getFileAndDirName()).toEqual({ relevantFile: 'utils', rootPath: '/Users/me/work/utils' }); // 动态修改mock返回值,模拟不同路径场景 (getFileAndDirName as jest.Mock).mockReturnValue({ relevantFile: 'tests', rootPath: '/Users/me/work/tests' }); expect(getFileAndDirName()).toEqual({ relevantFile: 'tests', rootPath: '/Users/me/work/tests' }); });
二、非理想但灵活的方案:自定义模块包装函数
通过Jest的全局配置修改模块包装逻辑,注入自定义的__filename和__dirname,但需要处理模块缓存问题。
- 修改jest.config.js:
module.exports = { setupFiles: ['./jest.setup.js'] };
- 编写jest.setup.js:
// 覆盖默认的模块包装函数,支持全局自定义路径变量 jest.mock('module', () => { const originalModule = jest.requireActual('module'); return { ...originalModule, wrapper: (exports, require, module, __filename, __dirname) => { const customFilename = global.__CUSTOM_FILENAME__ || __filename; const customDirname = global.__CUSTOM_DIRNAME__ || __dirname; return originalModule.wrapper(exports, require, module, customFilename, customDirname); } }; });
- 测试文件中使用:
it('全局模拟__filename和__dirname', () => { global.__CUSTOM_FILENAME__ = '/Users/me/work/utils/index.ts'; global.__CUSTOM_DIRNAME__ = '/Users/me/work/utils'; // 重置模块缓存,确保新的路径变量生效 jest.resetModules(); const { getFileAndDirName } = require('./utils'); expect(getFileAndDirName()).toEqual({ relevantFile: 'utils', rootPath: '/Users/me/work/utils' }); // 切换路径场景 global.__CUSTOM_FILENAME__ = '/Users/me/work/tests/index.ts'; global.__CUSTOM_DIRNAME__ = '/Users/me/work/tests'; jest.resetModules(); const { getFileAndDirName: updatedGetInfo } = require('./utils'); expect(updatedGetInfo()).toEqual({ relevantFile: 'tests', rootPath: '/Users/me/work/tests' }); });
三、保守方案:接受限制并规避
如果不想引入复杂的测试配置,或项目对测试复杂度有严格要求,可以选择以下方式规避:
- 提取依赖路径的纯逻辑:将
__filename.split('/')?.[3]这类路径处理逻辑单独拆分为纯函数,仅测试纯函数的正确性,而__filename/__dirname的注入部分依赖Node.js原生行为,不做单元测试。 - 集成测试验证:在实际文件路径下运行集成测试,验证整体逻辑的正确性,跳过单元级的变量模拟。
总结
优先选择jest.mock重写模块的方案,它既不侵入业务代码,又能保证测试隔离性;如果需要全局范围的模拟,可以尝试自定义模块包装函数的方案,但需注意模块缓存的影响;若项目场景简单,接受限制并调整测试策略也是合理的选择。
内容的提问来源于stack exchange,提问作者Zargold
相关产品推荐
相关产品推荐

