Testing Rspack

Rspack 的测试用例包括如下:

  • Rspack 核心测试用例,存放在 packages/rspack-test-tools/tests 文件夹下,会通过模拟构建流程以运行测试用例。通常情况下,都在此文件夹下添加测试用例。
  • Rspack 其他包的测试用例,存放在 packages/{name}/tests 文件夹下,仅当修改对应包时添加/修改。

运行测试

可以通过如下方式运行这些测试用例:

  • 根目录下运行 ./x test unitpnpm 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.errorscompilation.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 后缀文件生成快照