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

Jest ESM模式下mock导入函数报只读属性/常量赋值错误

Jest ESM模式下mock导入函数报错解决

问题现象

在ESM模式下使用Jest mock导入的工具函数时,会触发两类报错:

  • TypeError: Assignment to constant variable.
  • TypeError: Cannot assign to read only property 'sum' of object '[object Module]'
    预期效果为测试用例中mock函数可返回自定义设置的返回值。

已尝试的无效方案

尝试1

import { jest } from '@jest/globals'
import * as util from "./util.js"

it("TypeError: Cannot assign to read only property 'sum' of object '[object Module]'", () => {
  jest.spyOn(util, "sum").mockImplementation(() => { return 2 })
  
  expect(sum(1, 2)).toBe(2);
})

运行后触发只读属性赋值报错。

尝试2

import { jest } from '@jest/globals'
import { sum } from './util.js'

it("TypeError: Cannot assign to read only property 'sum' of object '[object Module]'", () => {
  jest.mock("./util.js", () => ({
    __esModule: true,
    sum: jest.fn().mockReturnValue(2),
  }));
  
  expect(sum(1, 2)).toBe(2);
})

运行后触发只读属性赋值报错。

尝试3

import { jest } from '@jest/globals'
import { sum } from "./util.js"

it("TypeError: Assignment to constant variable.", () => {
  sum = jest.fn(() => { return 2 })
  expect(sum(1, 2)).toBe(2);
})

运行后触发常量赋值报错。

现有配置

已按照Jest官方ESM指引完成基础配置:

package.json

{
  "type": "module",
  "scripts": {
    "test": "NODE_OPTIONS=--experimental-vm-modules jest"
  }
}

jest.config.js

module.exports = async () => {
  return {
    verbose: true,
    transform: {}
  };
};

可复现问题的仓库地址:https://github.com/fei1990a/jest-esm/tree/main

错误原因

ESM模块规范和CommonJS不同,静态导入的模块命名空间对象、具名导入都是只读绑定,Jest无法直接修改:

  1. 静态import拿到的模块是原生只读的Module Namespace Object,直接修改属性、调用spyOn都会触发只读错误
  2. ESM下具名导入是和原模块强绑定的常量,直接给导入的变量赋值会触发常量赋值错误
  3. jest.mock/jest.unstable_mockModule只会被提升到文件顶层执行,写在测试用例的块级作用域内不会生效,执行到对应代码时真实模块已经加载完成
  4. 静态import的执行优先级高于所有业务代码,就算在文件顶层写了mock,只要提前静态import了对应模块,拿到的还是未被mock的真实导出
  5. 尝试1额外存在作用域问题:代码中从未在当前作用域导入sum变量,就算mock执行成功,直接调用sum()也会触发变量未定义的引用错误

正确写法

方案1:全量mock模块

使用官方提供的ESM专用mock API jest.unstable_mockModule,所有被mock的模块必须在mock声明后用动态import()导入:

import { jest } from '@jest/globals'

// mock声明写在文件顶层,会被自动提升到所有静态import之后、动态import之前执行
jest.unstable_mockModule('./util.js', () => ({
  __esModule: true,
  sum: jest.fn(() => 2)
}))

it('mock sum返回指定值', async () => {
  // 等mock生效后再动态导入模块
  const { sum } = await import('./util.js')
  expect(sum(1, 2)).toBe(2)
})

方案2:spyOn局部mock单个方法

如果只想mock模块里的部分方法,保留其他方法的真实逻辑,用动态导入配合spyOn实现即可:

import { jest } from '@jest/globals'

it('spyOn mock sum方法', async () => {
  // 动态导入拿到Jest代理过的模块对象,支持属性改写
  const util = await import('./util.js')
  const spySum = jest.spyOn(util, 'sum').mockImplementation(() => 2)

  // 注意调用的时候要取模块对象上的sum,不要用静态导入的sum
  expect(util.sum(1, 2)).toBe(2)

  // 测试完成后恢复原实现,避免影响其他用例
  spySum.mockRestore()
})

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.03 08:15:26