Lerna monorepo中ts-jest v27无法mock本地scoped包函数如何解决?
问题根因
- 你导入的是
@local/package编译后的dist产物,tsc编译ESM/CommonJS模块时,导出的成员默认被设置为只读属性,直接修改、调用jest.spyOn都会触发仅存在getter的报错 - Lerna创建的本地包软链会被jest识别为
node_modules下的第三方模块,默认不会应用你当前测试包下的__mocks__规则、jest.mock规则,优先级低于真实模块解析 - ts-jest v27默认的模块转换逻辑会对静态导入的模块生成只读绑定,即使后续修改模块导出,也不会影响已经完成静态导入的代码
- 你直接用dist路径导入时,
moduleNameMapper、__mocks__目录的路径匹配规则和jest实际的模块解析逻辑不匹配,导致映射失效
解决方案
前置优化(推荐)
不要直接导入dist路径的文件,调整@local/package的package.json配置:
{ "main": "./dist/something.js", "module": "./dist/something.esm.js", "types": "./dist/something.d.ts" }
业务代码直接从包名导入即可:
import { someFunction } from '@local/package'
既符合npm包规范,也能降低jest mock的匹配复杂度。
可行方案1:调整jest配置映射源码
修改测试包的jest.config.js,添加如下配置:
module.exports = { // 其余原有配置保留 // 将@local包直接映射到对应包的src源码,跳过dist产物的只读限制 moduleNameMapper: { '^@local/([^/]+)/(.*)$': '<rootDir>/../$1/src/$2' // 请根据你的monorepo目录结构调整相对路径,比如你的@local/package放在根目录packages/package下,就写'<rootDir>/../../packages/$1/src/$2' }, // 允许jest转换@local下的本地包 transformIgnorePatterns: [ 'node_modules/(?!(@local)/)' ] }
之后在测试文件顶部直接声明mock即可:
// 必须写在所有导入语句之前 jest.mock('@local/package', () => ({ someFunction: jest.fn() })) import { someLogic } from './your-test-file' import { someFunction } from '@local/package' test('someLogic 会调用 someFunction', () => { someLogic() expect(someFunction).toHaveBeenCalled() })
可行方案2:动态导入绕过静态只读限制
如果必须保留dist路径导入,可以用jest.doMock配合动态导入实现mock:
test('someLogic 会调用 someFunction', async () => { // 先mock模块 jest.doMock('@local/package/dist/something', () => ({ someFunction: jest.fn() })) // 动态导入要测试的代码和目标模块,必须放在doMock之后 const { someLogic } = await import('./your-test-file') const { someFunction } = await import('@local/package/dist/something') someLogic() expect(someFunction).toHaveBeenCalled() })
可行方案3:劫持getter解决只读导出问题
如果仅需要mock单个方法,不想全量替换模块,可以直接劫持导出属性的getter:
// 写在所有导入语句之前 jest.mock('@local/package/dist/something', () => { // 保留其余模块的原有逻辑 const originalModule = jest.requireActual('@local/package/dist/something') return { ...originalModule, // 劫持someFunction的getter,返回mock函数 get someFunction() { return jest.fn(() => '自定义mock返回值') } } }) import { someLogic } from './your-test-file' import { someFunction } from '@local/package/dist/something' test('someLogic 会调用 someFunction', () => { someLogic() expect(someFunction).toHaveBeenCalled() })
内容的提问来源于stack exchange,提问作者Dmitri Pisarev
相关产品推荐
相关产品推荐

