Testing Rspack
Rspack 的测试用例包括如下:
- Rspack 核心测试用例,存放在
packages/rspack-test-tools/tests
文件夹下,会通过模拟构建流程以运行测试用例。通常情况下,都在此文件夹下添加测试用例。
- Rspack 其他包的测试用例,存放在
packages/{name}/tests
文件夹下,仅当修改对应包时添加/修改。
运行测试
可以通过如下方式运行这些测试用例:
- 根目录下运行
./x test unit
或 pnpm run test:unit
。
- 或在
packages/rspack-test-tools
目录下运行 npm run test
。
- 如需更新 snapshot,在
packages/rspack-test-tools
目录下运行 npm run test -- -u
。
- 如需传入特定 jest cli 参数,在
packages/rspack-test-tools
目录下运行 npm run test -- {args}
。
目录规范
文件夹 packages/rspack-test-tools/tests
的结构如下所示:
.
├── js #用于存放构建生成的产物和临时文件
├── __snapshots__ #用于存放测试快照
├── {Name}.test.js #常规测试的入口
├── {Name}.hottest.js #热更新流程测试入口
├── {Name}.difftest.js #产物对比测试入口
├── {name}Cases #测试用例存放目录
└── fixtures #通用测试文件
{Name}.test.js
作为测试的入口文件,会遍历 {name}Cases
并运行其中的用例。因此当您需要添加/修改测试用例时,请根据需要测试的功能类型在相应的 {name}Cases
文件夹下添加用例。
测试类型
目前已有的测试类型有:
- Normal:用于测试配置无关的核心构建流程。当您的测试无需添加
rspack.config.js
时可使用此测试类型。
- Config:用于测试构建配置项。如果你的测试需要通过
rspack.config.js
添加特定配置才可以运行时,且不符合其他场景时可使用此测试类型。
- Hot:用于测试 Hot Module Replacement(HMR)是否正确运行。HotNode 会固定使用
target=async-node
,HotWeb 会固定使用 target=web
,HotWorker 会固定使用 target=webworker
。
- HotSnapshot:用于测试 Hot Module Replacement(HMR) 能否生成正确的中间产物。与Hot类型测试共用测试用例。
- Watch:用于测试 Watch 模式下修改文件后的增量编译。
- StatsOutput:用于测试构建结束后控制台输出的日志。
- StatsAPI:用于测试构建结束后生成的 Stats 对象。
- Diagnostic:用于测试构建过程中产生的警告/错误的格式化输出信息。
- Hash:用于测试 Hash 能否正确生成。
- Compiler:用于测试 Compiler/Compilation 对象的 API。
- Defaults:用于测试配置项之间的联动。
- Error:用于测试
compilation.errors
和 compilation.warnings
的互操作。
- Hook:用于测试各种 hook 能否正确工作。
- TreeShaking:用于测试 Tree Shaking 相关功能。
- Builtin:用于测试内置原生实现的插件。
请优先在以上测试类型中添加用例。
Normal
测试入口 |
tests/Normal.test.js |
用例目录 |
tests/normalCases |
产物目录 |
tests/js/normal |
默认配置 |
NormalProcessor |
产物运行 |
是 |
用例的编写与常规的 rspack 项目相同,但不包含 rspack.config.js
文件,会使用固定的配置构建。
Config
测试入口 |
tests/Config.test.js |
用例目录 |
tests/configCases |
产物目录 |
tests/js/config |
默认配置 |
ConfigProcessor |
产物运行 |
是 |
此测试用例与常规的 rspack 项目相同,可通过添加 rspack.config.js
来指定构建配置,并可通过添加 test.config.js
来控制测试运行时的各种行为,其结构如下:
test.config.js
1type TConfigCaseConfig = {
2 noTest?: boolean; // 不运行测试产物并结束测试
3 beforeExecute?: () => void; // 运行产物前回调
4 afterExecute?: () => void; // 运行产物后回调
5 moduleScope?: (ms: IBasicModuleScope) => IBasicModuleScope; // 运行产物时的模块上下文变量
6 findBundle?: (
7 // 运行产物时的产物获取函数,可以更细粒度的控制产物
8 index: number, // muli compiler 场景时的 compiler 序号
9 options: TCompilerOptions<T>, // 构建配置对象
10 ) => string | string[];
11 bundlePath?: string[]; // 运行产物时的产物文件名称(优先级低于 findBundle)
12 nonEsmThis?: (p: string | string[]) => Object; // CJS 产物运行时的 this 对象,若不指定则默认为当前模块的 module.exports
13 modules?: Record<string, Object>; // 运行产物时预先添加的模块,require 时会优先从此处读取
14 timeout?: number; // 用例的超时时间
15};
16
17/** @type {import("../../../..").TConfigCaseConfig} */
18module.exports = {
19 // ...
20};
Hot
测试入口 |
Hot{Target}.test.js |
用例目录 |
tests/hotCases |
产物目录 |
tests/js/hot-{target} |
默认配置 |
HotProcessor |
产物运行 |
是 |
此测试用例与常规的 rspack 项目相同,可通过添加 rspack.config.js
来指定构建配置
对应的在变更的文件内通过 ---
分割变更前后的代码:
file.js
module.exports = 1; // 初始构建
---
module.exports = 2; // 首次热更新
---
module.exports = 3; // 第二次热更新
在用例的代码中,通过 NEXT
方法控制文件变更时机,并在其中添加测试代码:
index.js
import value from './file';
it('should hot update', done => {
expect(value).toBe(1);
// 使用 packages/rspack-test-tools/tests/hotCases/update.js 触发更新
NEXT(
require('../../update')(done, true, () => {
expect(value).toBe(2);
NEXT(
require('../../update')(done, true, () => {
expect(value).toBe(3);
done();
}),
);
}),
);
});
module.hot.accept('./file');
HotSnapshot
测试入口 |
HotSnapshot.hottest.js |
用例目录 |
tests/hotCases |
产物目录 |
tests/js/hot-snapshot |
默认配置 |
与 Hot 相同 |
产物运行 |
是 |
与 Hot{Target}
测试使用相同的测试用例。并在用例文件夹下生成 __snapshots__/{target}/{step}.snap.txt
文件,用于对每一次 HMR 的增量产物进行 snapshot 测试。
Snapshot 结构如下:
- Changed Files:引发本次 HMR 构建的源码文件
- Asset Files:本次 HMR 构建的产物文件
- Manifest:本次 HMR 构建的
hot-update.json
元数据文件内容,其中
"c"
:本次 HMR 需要更新的 chunk 的 id
"r"
:本次 HMR 需要移除的 chunk 的 id
"m"
:本次 HMR 需要移除的 module 的 id
- Update:本次 HMR 构建的
hot-update.js
补丁文件信息,其中:
- Changed Modules:补丁中包含的模块列表
- Changed Runtime Modules:补丁中包含的 runtime 模块列表
- Changed Content:补丁代码的快照
Watch
入口文件 |
Watch.test.js |
用例目录 |
tests/watchCases |
产物目录 |
tests/js/watch |
默认配置 |
WatchProcessor |
产物运行 |
是 |
由于 Watch 构建需要分多步进行,可通过添加 rspack.config.js
来指定构建配置。其用例的目录结构较为特殊,会以自增的数字表示变更批次:
用例目录
.
├── 0 # WATCH_STEP=0,用例初始代码
├── 1 # WATCH_STEP=1,第一次变更的差异文件
├── 2 # WATCH_STEP=2,第二次变更的差异文件
└── rspack.config.js
同时在测试的代码中,可以通过 WATCH_STEP
变量获取当前的变更批次数字。
StatsOutput
测试入口 |
StatsOutput.test.js |
用例目录 |
tests/statsOutputCases |
产物目录 |
tests/js/statsOutput |
默认配置 |
StatsProcessor |
产物运行 |
否 |
用例的编写与常规的 rspack 项目相同,运行后会将控制台输出信息生成快照并存放在 rspack-test-tools/tests/__snapshots__/StatsOutput.test.js.snap
中。
Tip
由于部分 StatsOutput 测试用例包含 hash。因此当你修改了产物代码时,请通过 -u
参数刷新这些用例的快照。
Stats API
入口文件 |
StatsAPI.test.js |
用例目录 |
tests/statsAPICases |
产物目录 |
无 |
默认配置 |
无 |
产物运行 |
否 |
此测试固定使用 rspack-test-tools/tests/fixtures
作为构建的源码,因此用例以单文件编写,其输出结构如下:
{case}.js
1type TStatsAPICaseConfig = {
2 description: string; // 用例描述
3 options?: (context: ITestContext) => TCompilerOptions<T>; // 用例构建配置
4 build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // 用例构建方式
5 check?: (stats: TCompilerStats<T>, compiler: TCompiler<T>) => Promise<void>; // 用例的 stats 检测函数
6}
7
8/** @type {import('../..').TStatsAPICaseConfig} */
9module.exports = {
10 // ...
11}
Diagnostic
入口文件 |
Diagnostics.test.js |
用例目录 |
tests/diagnosticsCases |
产物目录 |
tests/js/diagnostics |
默认配置 |
DiagnosticProcessor |
产物运行 |
否 |
此测试用例与常规的 rspack 项目相同,可通过添加 rspack.config.js
来指定构建配置,但额外会在用例目录下添加 stats.err
文件用于存储警告/错误的快照,如需刷新请使用 -u
参数。
Hash
入口文件 |
Hash.test.js |
用例目录 |
tests/hashCases |
产物目录 |
无 |
默认配置 |
HashProcessor |
产物运行 |
否 |
此测试用例与常规的 rspack 项目相同,但额外会在用例目录下添加 test.config.js
文件,并指定 validate()
方法用于在构建结束后检测 stats 对象中的 hash 信息:
test.config.js
1type THashCaseConfig = {
2 validate?: (stats: TCompilerStats<T>) => void;
3};
4
5/** @type {import('../..').THashCaseConfig} */
6module.exports = {
7 // ...
8};
Compiler
入口文件 |
Compiler.test.js |
用例目录 |
tests/compilerCases |
产物目录 |
无 |
默认配置 |
无 |
产物运行 |
否 |
此测试固定使用 rspack-test-tools/tests/fixtures
作为构建的源码,因此用例以单文件编写,其输出结构如下:
{case.js}
1interface TCompilerCaseConfig {
2 description: string; // 用例描述
3 options?: (context: ITestContext) => TCompilerOptions<T>; // 用例构建配置
4 compiler?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // 用例 compiler 创建方式
5 build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // 用例构建方式
6 check?: (context: ITestContext, compiler: TCompiler<T>, stats: TCompilerStats<T>) => Promise<void>; // 用例的检测函数
7}
8
9/** @type {import('../..').TCompilerCaseConfig} */
10module.exports = {
11 // ...
12};
Defaults
入口文件 |
Defaults.test.js |
用例目录 |
tests/defaultCases |
产物目录 |
无 |
默认配置 |
无 |
产物运行 |
否 |
此测试不会执行真实的构建,仅会生成构建配置并观察与默认配置的差异。基础的默认配置会生成快照并存放在 rspack-test-tools/tests/__snapshots__/Defaults.test.js.snap
中。
此测试固定使用 rspack-test-tools/tests/fixtures
作为构建的源码,因此用例以单文件编写,其输出结构如下:
{case}.js
1interface TDefaultsCaseConfig {
2 description: string; // 用例描述
3 cwd?: string; // 用例的生成构建配置时的 process.cwd,默认为 `rspack-test-tools` 目录
4 options?: (context: ITestContext) => TCompilerOptions<ECompilerType.Rspack>; // 用例构建配置
5 diff: (diff: jest.JestMatchers<Diff>, defaults: jest.JestMatchers<TCompilerOptions<ECompilerType.Rspack>>) => Promise<void>; // 与默认配置之间的差异
6}
7
8/** @type {import('../..').TDefaultsCaseConfig} */
9module.exports = {
10 // ...
11};
Error
入口文件 |
Error.test.js |
用例目录 |
tests/errorCases |
产物目录 |
无 |
默认配置 |
ErrorProcessor |
产物运行 |
否 |
此测试的用例固定使用 rspack-test-tools/tests/fixtures
作为构建的源码,因此测试用例以特定的配置结构编写:
{case}.js
1interface TErrorCaseConfig {
2 description: string; // 用例描述
3 options?: (options: TCompilerOptions<T>, context: ITestContext) => TCompilerOptions<T>; // 用例配置
4 build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // 用例构建方式
5 check?: (stats: TStatsDiagnostics) => Promise<void>; // 用例的检测函数
6}
7
8/** @type {import('../..').TErrorCaseConfig} */
9module.exports = {
10 // ...
11};
Hook
入口文件 |
Hook.test.js |
用例目录 |
tests/hookCases |
产物目录 |
无 |
默认配置 |
HookProcessor |
产物运行 |
否 |
会记录 hook 的出入参并存放在快照 hooks.snap.txt
中,最终产物代码的快照存放在 output.snap.txt
中。
此测试的用例固定使用 rspack-test-tools/tests/fixtures
作为构建的源码,因此测试用例以特定的配置结构编写:
{case}/test.js
1interface THookCaseConfig {
2 description: string; // 用例描述
3 options?: (options: TCompilerOptions<T>, context: ITestContext) => TCompilerOptions<T>; // 用例配置
4 compiler?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // 创建 compiler 实例后回调
5 check?: (context: ITestContext) => Promise<void>; // 构建完成后回调
6}
7
8/** @type {import("../../../..").THookCaseConfig} */
9module.exports = {
10 // ...
11};
TreeShaking
入口文件 |
TreeShaking.test.js |
用例目录 |
tests/treeShakingCases |
产物目录 |
tests/js/treeShaking |
默认配置 |
TreeShakingProcessor |
产物运行 |
否 |
此测试用例与常规的 rspack 项目相同,可通过添加 rspack.config.js
来指定构建配置,但会将最终产物生成快照并存放在 __snapshots__/treeshaking.snap.txt
中。
Builtin
入口文件 |
Builtin.test.js |
用例目录 |
tests/builtinCases |
产物目录 |
tests/js/builtin |
默认配置 |
BuiltinProcessor |
产物运行 |
否 |
此测试用例与常规的 rspack 项目相同,可通过添加 rspack.config.js
来指定构建配置。
但根据目录的不同,会将不同的产物生成快照并存放在 __snapshots__/output.snap.txt
中:
- plugin-css:将
.css
后缀文件生成快照
- plugin-css-modules:将
.css
和 .js
后缀文件生成快照
- plugin-html:将
.html
后缀文件生成快照
- 其他:将
.js
后缀文件生成快照