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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.29 16:57:03