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

使用Jest 29.7.0 Mock模块函数时遇只读属性赋值错误求助

解决Jest 29.7.0中ESM模块Async函数Mock失败的问题

你遇到的所有报错本质是ESM模块的导出是只读绑定,并非普通可修改的对象属性,所以针对CommonJS模块的jest.spyOn、直接赋值jest.fn()、Object.defineProperty等方法在ESM环境下不适用。以下是几种可行的解决方案:

方案1:使用jest.mock直接模拟ESM模块

这是ESM环境下最直接的mock方式,注意必须先执行jest.mock再导入被测试模块,否则ESM静态加载机制会优先加载原模块导致mock失效。

示例代码

message.js(原模块)

export async function send(content) {
  // 原业务逻辑
  console.log('发送消息:', content);
  return 'success';
}

handler.js

import { send } from './message.js';

export async function handle(data) {
  const result = await send(data);
  return result;
}

handler.test.js

// 先mock模块,再导入目标模块
jest.mock('./message.js', () => ({
  send: jest.fn().mockResolvedValue('mock-success')
}));

import { handle } from './handler.js';
import { send } from './message.js';

test('handle方法调用mock的send函数', async () => {
  const result = await handle('test-data');
  
  expect(send).toHaveBeenCalledWith('test-data');
  expect(result).toBe('mock-success');
});

方案2:动态修改Mock返回值(结合jest.doMock)

如果需要在不同测试用例中动态调整mock行为,可以用jest.doMock配合模块缓存重置,实现灵活的mock配置。

test('动态修改send的mock返回值', async () => {
  jest.resetModules(); // 重置模块缓存,避免跨用例污染
  
  const mockSend = jest.fn().mockResolvedValue('first-mock');
  jest.doMock('./message.js', () => ({
    send: mockSend
  }));
  
  // 动态导入模块
  const { handle } = await import('./handler.js');
  const { send } = await import('./message.js');
  
  let result = await handle('test1');
  expect(send).toHaveBeenCalledWith('test1');
  expect(result).toBe('first-mock');
  
  // 修改mock返回值
  mockSend.mockResolvedValue('second-mock');
  result = await handle('test2');
  expect(result).toBe('second-mock');
});

方案3:通过Babel转为CommonJS(兼容传统Mock方式)

如果项目允许兼容CommonJS,可通过Babel将ESM转为CommonJS,这样就能使用jest.spyOn等传统mock方法。

步骤1:安装依赖

npm install --save-dev @babel/core @babel/preset-env babel-jest

步骤2:配置babel.config.json

{
  "presets": [
    ["@babel/preset-env", { "targets": { "node": "current" } }]
  ]
}

步骤3:配置jest.config.js(如果存在)

module.exports = {
  transform: {
    '^.+\\.js$': 'babel-jest'
  }
};

测试代码示例

import { handle } from './handler.js';
import * as message from './message.js';

test('用spyOn mock send函数', async () => {
  const spy = jest.spyOn(message, 'send').mockResolvedValue('spy-mock');
  
  const result = await handle('test');
  
  expect(spy).toHaveBeenCalledWith('test');
  expect(result).toBe('spy-mock');
  
  spy.mockRestore(); // 测试后恢复原函数
});

常见问题排查

  • 确保运行命令正确:NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" jest,这是ESM模式下Jest运行的必要配置。
  • 禁止在导入模块后执行jest.mock,ESM静态加载机制会导致mock无法覆盖原模块。
  • 若出现Cannot redefine property报错,说明你尝试修改ESM的只读导出绑定,必须改用模块级别的mock方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 19:41:06