使用 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.js
和 Alpha-xxxxxx.js
。原始文件中的一些功能被提取到这个“中间”块 (Alpha-xxxxxx.js
)。
在我的单元测试中,当模拟任何移入中间文件 Alpha-xxxxxx.js
的方法时,jest
似乎实际上是从这个“中间”模块而不是顶级模块加载模块.
这会导致测试失败。
例如jest.mock('MyLibrary/dist/Alpha')
不起作用,因为模块实际上是从 Alpha-xxxxxx.js
而不是 Alpha.js
加载的
详细解释与示例
我在 MyLibrary
中有两个模块 Alpha.js
和 Beta.js
。
MyLibrary/Alpha.js
export const Aaa = () => {
...
}
...
MyLibrary/Beta.js
import { Aaa } from './Alpha';
...
export const Bbb = () => {
...
}
...
汇总生成的编译输出
捆绑时,rollup.js 将 Alpha.js
分成 2 个块 Alpha.js
和 Alpha-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
目录的步骤
我正在编写一个 React 库 MyLibrary
并将其与 Rollup.js 2.58.3
. I use jest
捆绑在一起用于单元测试。
问题的快速总结
我无法使用 jest
从我的库中模拟模块。这是由于汇总“编译”我的代码的方式。
Rollup 使用其代码拆分功能来创建许多块。在一个示例中,rollup 将 Alpha.js
分成两个块:Alpha.js
和 Alpha-xxxxxx.js
。原始文件中的一些功能被提取到这个“中间”块 (Alpha-xxxxxx.js
)。
在我的单元测试中,当模拟任何移入中间文件 Alpha-xxxxxx.js
的方法时,jest
似乎实际上是从这个“中间”模块而不是顶级模块加载模块.
这会导致测试失败。
例如jest.mock('MyLibrary/dist/Alpha')
不起作用,因为模块实际上是从 Alpha-xxxxxx.js
而不是 Alpha.js
详细解释与示例
我在 MyLibrary
中有两个模块 Alpha.js
和 Beta.js
。
MyLibrary/Alpha.js
export const Aaa = () => {
...
}
...
MyLibrary/Beta.js
import { Aaa } from './Alpha';
...
export const Bbb = () => {
...
}
...
汇总生成的编译输出
捆绑时,rollup.js 将 Alpha.js
分成 2 个块 Alpha.js
和 Alpha-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
目录的步骤