跳至主内容
版本:30.0

ECMAScript 模块

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

注意

Jest 提供了对 ECMAScript 模块(ESM)的实验性支持。

当前实现可能存在缺陷且功能不完善。请通过问题跟踪相关标签查看最新状态。

请注意,Jest 实现 ESM 支持所使用的 API 在 Node.js 中仍被视为实验性功能(截至 18.8.0 版本)。

了解上述警告后,以下是启用测试中 ESM 支持的方法。

  1. 确保通过传递 transform: {} 禁用代码转换,或者将转换器配置为输出 ESM 而非默认的 CommonJS(CJS)。

  2. 使用 --experimental-vm-modules 参数执行 node,例如:
    node --experimental-vm-modules node_modules/jest/bin/jest.js

    NODE_OPTIONS="$NODE_OPTIONS --experimental-vm-modules" npx jest 等。

    在 Windows 系统中,可使用 cross-env 设置环境变量。

    若使用 Yarn,可执行:
    yarn node --experimental-vm-modules $(yarn bin jest)
    此命令同样适用于 Yarn Plug'n'Play 环境。

    若代码库包含从 *.wasm 文件进行的 ESM 导入,则无需node 传递 --experimental-wasm-modules。Jest 当前对 WebAssembly 导入的实现依赖于实验性 VM 模块,但这在未来可能改变。

  3. 此外,我们会尝试遵循 node 激活 "ESM 模式" 的逻辑(例如检查 package.json 中的 type 字段或 .mjs 文件),详见其官方文档

  4. 若需将其他文件扩展名(如 .jsx.ts)视为 ESM,请使用 extensionsToTreatAsEsm 选项

ESM 与 CommonJS 的差异

主要差异已在 Node.js 文档中说明。除文档提及的内容外,Jest 会向所有执行文件注入一个特殊变量 —— jest 对象。在 ESM 中访问此对象时,需从 @jest/globals 模块导入或使用 import.meta

import {jest} from '@jest/globals';

jest.useFakeTimers();

// etc.

// alternatively
import.meta.jest.useFakeTimers();

// jest === import.meta.jest => true

ESM 中的模块模拟

由于 ESM 在解析代码前会先处理静态 import 语句,CommonJS 中 jest.mock 调用的提升机制在 ESM 中无效。要在 ESM 中模拟模块,需在 jest.mock 调用后使用 require 或动态 import() 加载被模拟模块 —— 此规则同样适用于加载被模拟模块的依赖模块。

ESM 模拟通过 jest.unstable_mockModule 实现。顾名思义,该 API 仍在开发中,请关注此问题获取更新。

jest.unstable_mockModule 的用法与 jest.mock 基本相同,主要区别在于:必须提供工厂函数,且该函数可以是同步或异步的:

import {jest} from '@jest/globals';

jest.unstable_mockModule('node:child_process', () => ({
execSync: jest.fn(),
// etc.
}));

const {execSync} = await import('node:child_process');

// etc.

ESM 中的模块取消模拟

esm-module.mjs
export default () => {
return 'default';
};

export const namedFn = () => {
return 'namedFn';
};
esm-module.test.mjs
import {jest, test} from '@jest/globals';

test('test esm-module', async () => {
jest.unstable_mockModule('./esm-module.js', () => ({
default: () => 'default implementation',
namedFn: () => 'namedFn implementation',
}));

const mockModule = await import('./esm-module.js');

console.log(mockModule.default()); // 'default implementation'
console.log(mockModule.namedFn()); // 'namedFn implementation'

jest.unstable_unmockModule('./esm-module.js');

const originalModule = await import('./esm-module.js');

console.log(originalModule.default()); // 'default'
console.log(originalModule.namedFn()); // 'namedFn'

/* !!! WARNING !!! Don`t override */
jest.unstable_mockModule('./esm-module.js', () => ({
default: () => 'default override implementation',
namedFn: () => 'namedFn override implementation',
}));

const mockModuleOverride = await import('./esm-module.js');

console.log(mockModuleOverride.default()); // 'default implementation'
console.log(mockModuleOverride.namedFn()); // 'namedFn implementation'
});

模拟 CommonJS 模块

对于模拟 CommonJS 模块,应继续使用 jest.mock。参考以下示例:

main.cjs
const {BrowserWindow, app} = require('electron');

// etc.

module.exports = {example};
main.test.cjs
import {createRequire} from 'node:module';
import {jest} from '@jest/globals';

const require = createRequire(import.meta.url);

jest.mock('electron', () => ({
app: {
on: jest.fn(),
whenReady: jest.fn(() => Promise.resolve()),
},
BrowserWindow: jest.fn().mockImplementation(() => ({
// partial mocks.
})),
}));

const {BrowserWindow} = require('electron');
const exported = require('./main.cjs');

// alternatively
const {BrowserWindow} = (await import('electron')).default;
const exported = await import('./main.cjs');

// etc.