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

3
我正在编写一个名为MyLibrary的React库,并使用Rollup.js 2.58.3进行打包。我使用jest进行单元测试。

问题简要说明

我无法使用jest模拟我的库中的模块。这是由于rollup "编译"代码的方式造成的。

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生成的编译输出

当使用rollup.js打包时,Alpha.js会被拆分成两个块: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(),
}));

我应该如何在Jest测试中正确模拟我的库中的Alpha


1
你应该从源文件而不是生成的文件中运行测试。在你的测试中使用import { Bbb } from 'MyLibrary/Beta';等语句。 - hjrshng
因为您只有两个模块,所以始终只会生成一个单一的块模块,对吗? - jsejcksn
@jsejcksn,“MyLibrary”有多个模块。我在上面给出了一个简化的例子。Rollup会根据它在库中其他模块中的使用情况自动将其分成不同的块。 - wiseindy
@hjrshng 我们从 dist 文件夹中导入生成的文件到我们的应用程序中。在测试中使用相同的导入方式不是很合理吗? - wiseindy
你是否了解足够的目标块信息,可以通过文件名称部分来识别它(忽略哈希值)? - jsejcksn
1个回答

2
似乎您需要模拟一个文件名基于确定性的模块,但包含每次构建时更改的哈希后缀。您没有提供输出模块块的完整文件名模式,因此我将使用一个假设的示例:
假设您需要查找的块模块按以下模式发出:
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(),
}));

如果需要,您甚至可以在构建后的步骤中使用该函数来定位块文件名,并将其作为JSON工件发出:然后在测试中导入JSON并使用文件名值。这将允许您跳过每次运行测试时枚举“dist”目录的步骤。

我注意到您之前接受了这个答案并授予了赏金,但是之后又取消了接受。您遇到了其他问题吗? - jsejcksn
是的,对不起,我只是想在确认之前再次检查一下。我现在已经接受了。非常感谢你的帮助! - wiseindy
很高兴你解决了它,@wiseindy。 - jsejcksn

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接