使用 Rollup.js 时如何在 Jest 测试中正确模拟函数

How to correctly mock function in Jest tests when using Rollup.js

我正在编写一个 React 库 MyLibrary 并将其与 Rollup.js 2.58.3. I use jest 捆绑在一起用于单元测试。

问题的快速总结

我无法使用 jest 从我的库中模拟模块。这是由于汇总“编译”我的代码的方式。

Rollup 使用其代码拆分功能来创建许多块。在一个示例中,rollup 将 Alpha.js 分成两个块:Alpha.jsAlpha-xxxxxx.js。原始文件中的一些功能被提取到这个“中间”块 (Alpha-xxxxxx.js)。

在我的单元测试中,当模拟任何移入中间文件 Alpha-xxxxxx.js 的方法时,jest 似乎实际上是从这个“中间”模块而不是顶级模块加载模块.

这会导致测试失败。

例如jest.mock('MyLibrary/dist/Alpha') 不起作用,因为模块实际上是从 Alpha-xxxxxx.js 而不是 Alpha.js

加载的

详细解释与示例

我在 MyLibrary 中有两个模块 Alpha.jsBeta.js

MyLibrary/Alpha.js

export const Aaa = () => {
  ...
}
...

MyLibrary/Beta.js

import { Aaa } from './Alpha';
...
export const Bbb = () => {
  ...
}
...

汇总生成的编译输出

捆绑时,rollup.js 将 Alpha.js 分成 2 个块 Alpha.jsAlpha-xxxxxx.js。由于代码拆分,Beta.js 的编译版本现在看起来像这样:

MyLibrary/dist/Beta.js

import { Aaa } from './Alpha-xxxxxx';
...
export const Bbb = () => {
  ...
}
...

这个来自 MyLibrary 的编译模块已导入到 MyApp 中,应用似乎可以正确导入它并且运行良好。

MyApp/index.js

import { Bbb } from 'MyLibrary/dist/Beta'
...

问题

下面的 jest 测试失败,因为它没有正确模拟 Aaa 导出。

MyApp/index.test.js

import { Bbb } from 'MyLibrary/dist/Beta';

jest.mock('MyLibrary/dist/Alpha', () => ({
  Aaa: jest.fn(),
}));

但是,如果我模拟 rollup 生成的中间块,它就可以工作。

import { stuff } from 'MyLibrary/dist/Beta';

jest.mock('MyLibrary/dist/Alpha-xxxxxx', () => ({
  Aaa: jest.fn(),
}));

如何在玩笑测试中从我的库中正确模拟 Alpha

听起来您需要模拟一个模块,其文件名基础是确定的,但包含一个哈希后缀,该后缀随每次构建而变化。您没有提供输出模块块文件名的完整模式,所以我将使用一个假设的例子:

假设您需要查找的块模块以这种模式发出:

Alpha-b38ca4f6.js

意思是文字 Alpha- 后跟长度为 8 的字母数字散列,然后是扩展名 .js

你可以用这个正则表达式表达:

^Alpha-[a-z0-9]{8}.js$

您可以使用辅助函数在将文件名传递给 Jest 之前找到正确的文件名。该函数应该接受一个目录和一个用于匹配文件模式的正则表达式。这是一个例子:

MyLibrary/utils.mjs:

import path from 'path';
import {promises as fs} from 'fs';

export async function findFileByPattern (dir, regex) {
  const fileNames = (await fs.readdir(dir, {withFileTypes: true}))
    .filter(entry => entry.isFile())
    .map(entry => entry.name);

  for (const fileName of fileNames) {
    if (!regex.test(fileName)) continue;
    const {name} = path.parse(fileName);
    return path.join(dir, name);
  }

  throw new Error('File not found matching the provided pattern');
}

MyApp/index.test.js:

import { Bbb } from 'MyLibrary/dist/Beta';
import { findFileByPattern } from './utils';

const chunkFileName = await findFileByPattern('MyLibrary/dist', /^Alpha-[a-z0-9]{8}.js$/);

jest.mock(chunkFileName, () => ({
  Aaa: jest.fn(),
}));

如果需要,您甚至可以使用该函数在 post-构建步骤中定位块文件名,并将值作为 JSON 工件发出:然后导入 JSON 在你的测试中使用文件名值。这将允许您在每次测试 运行.

时跳过枚举 dist 目录的步骤