Because Rspack uses a mix of Rust and Node.js code, different testing strategies are used for each.
Rust test cases are only suitable for unit testing. To test the complete build process, please add Node.js test cases.
You can run the Rust code's test cases using ./x test rust or cargo test.
Test cases are written within the Rust code. For example:
fn add(a: u8, b: u8) -> u8 {
a + b
}
#[test]
fn test_add() {
assert_eq!(add(1, 2), 3);
}For more information, please refer to: Rust: How to Write Tests
Rspack's test cases include the following:
tests/rspack-test folder and will run the test cases by simulating the build process. In general, test cases should be added in this folder.packages/{name}/tests folder and should only be added or modified when modifying that package.You can run Rspack tests by running ./x test unit or pnpm run test:unit at the root folder.
You can also go to the tests/rspack-test folder and run npm run test to run test cases and add some arguments:
-u, like npm run test -- -u-t, like npm run test -- -t configCases/asset to only run test cases from the tests/rspack-test/configCases/asset folder. Pattern matching supports regex, see rstest for details.You can run these test cases in the following ways:
./x test unit or pnpm run test:unit from the root directory.npm run test from the tests/rspack-test directory.npm run test -- -u from the tests/rspack-test directory.npm run test -- {args} from the tests/rspack-test directory.npm run test -- -t path-of-spec from the tests/rspack-test directory.
npm run test -- -t configCases/asset to only run test cases from the tests/rspack-test/configCases/asset folder (config will be automatically mapped to configCases, and other folders will work in a similar way).NAPI_RS_FORCE_WASI=1: Forces the use of Rspack Wasm instead of native bindingWASM=1: Enables Wasm-specific test configurationsNODE_NO_WARNINGS=1: Disables Node.js Wasm warnings.NAPI_RS_FORCE_WASI=1 WASM=1 NODE_NO_WARNINGS=1 pnpm run test:unitThe structure of the tests/rspack-test folder is as follows:
.
├── js # Used to store build artifacts and temporary files
├── __snapshots__ # Used to store test snapshots
├── {Name}.test.js # Entry for normal testing
├── {Name}.hottest.js # Entry for hot snapshot testing
├── {Name}.difftest.js # Entry for diff testing
├── {name}Cases # Directory to store test cases
└── fixtures # General test filesThe {Name}.test.js is the entry file for tests, which will walk the {name}Cases folder and run cases in it. Therefore, when you need to add or modify test cases, add them to the relevant {name}Cases folder based on the type of testing.
The existing test types are:
rspack.config.js.rspack.config.js to run and does not fit other scenarios, use this test type.target=async-node, HotWeb with a fixed target=web, and HotWorker with a fixed target=webworker.compilation.errors and compilation.warnings.Please prioritize adding test cases within the above test types.
| Test Entry | tests/Normal.test.js |
|---|---|
| Case Directory | tests/normalCases |
| Output Directory | tests/js/normal |
| Default Configuration | NormalProcessor |
| Run Output | Yes |
The writing of the case is the same as a regular rspack project, but it does not include the rspack.config.js file and will use the provided configuration for building.
| Test Entry | tests/Config.test.js |
|---|---|
| Case Directory | tests/configCases |
| Output Directory | tests/js/config |
| Default Configuration | ConfigProcessor |
| Run Output | Yes |
This test case is similar to a regular rspack project. You can specify the build configuration by adding a rspack.config.js and control various behaviors during testing by adding a test.config.js. The structure of the test.config.js file is as follows:
type TConfigCaseConfig = {
noTests?: boolean; // Do not run the test output and end the test
beforeExecute?: () => void; // Callback before running the output
afterExecute?: () => void; // Callback after running the output
moduleScope?: (ms: IModuleScope) => IModuleScope; // Module context variables when running the output
findBundle?: (
// Function for obtaining output when running the output, can control the output at a finer granularity
index: number, // Compiler index in multi-compiler scenario
options: TCompilerOptions<T>, // Build configuration object
) => string | string[];
bundlePath?: string[]; // Output file name when running the output (prior to findBundle)
nonEsmThis?: (p: string | string[]) => Object; // this object during CJS output runtime, defaults to current module's module.exports if not specified
modules?: Record<string, Object>; // Pre-added modules when running the output, will be prioritized when required
timeout?: number; // Timeout for the test case
};
/** @type {import("../../../..").TConfigCaseConfig} */
module.exports = {
// ...
};| Test Entry | Hot{Target}.test.js |
|---|---|
| Case Directory | tests/hotCases |
| Output Directory | tests/js/hot-{target} |
| Default Configuration | HotProcessor |
| Run Output | Yes |
This test case is similar to a regular rspack project. You can specify the build configuration by adding a rspack.config.js.
And also, within the file that has changed, use --- to separate the code before and after the change:
module.exports = 1; // Initial build
---
module.exports = 2; // First hot update
---
module.exports = 3; // Second hot updateIn the test case code, use the NEXT_HMR method to control the timing of file changes and add test code within it:
import value from './file';
it('should hot update', done => {
expect(value).toBe(1);
// Use tests/rspack-test/hotCases/update.js to trigger update
await NEXT_HMR();
expect(value).toBe(2);
await NEXT_HMR();
expect(value).toBe(3);
});
module.hot.accept('./file');| Test Entry | HotSnapshot.hottest.js |
|---|---|
| Case Directory | tests/hotCases |
| Output Directory | tests/js/hot-snapshot |
| Default Configuration | Same as Hot |
| Run Output | Yes |
Uses the same test cases as Hot{Target}, and generates a __snapshots__/{target}/{step}.snap.txt file in the case folder to perform snapshot testing on the incremental artifacts of each HMR.
The snapshot structure is as follows:
hot-update.json metadata file for this HMR build, where:
"c": Id of the chunks to be updated in this HMR"r": Id of the chunks to be removed in this HMR"m": Id of the modules to be removed in this HMRhot-update.js patch file for this HMR build, including:
| Entry File | Watch.test.js |
|---|---|
| Case Directory | tests/watchCases |
| Output Directory | tests/js/watch |
| Default Configuration | WatchProcessor |
| Run Output | Yes |
As the Watch build needs to be performed in multiple steps, you can specify the build configuration by adding a rspack.config.js. The directory structure of its cases is special and will use incrementing numbers to represent change batches:
.
├── 0 # WATCH_STEP=0, initial code for the case
├── 1 # WATCH_STEP=1, diff files for the first change
├── 2 # WATCH_STEP=2, diff files for the second change
└── rspack.config.jsIn the test code, you can use the WATCH_STEP variable to get the current batch number of changes.
| Test Entry | StatsOutput.test.js |
|---|---|
| Case Directory | tests/statsOutputCases |
| Output Directory | tests/js/statsOutput |
| Default Configuration | StatsProcessor |
| Run Output | No |
The writing of the cases is the same as in a regular rspack project. After running, the console output information will be captured in snapshots and stored in tests/rspack-test/__snapshots__/StatsOutput.test.js.snap.
As some StatsOutput test cases contain hashes, when you modify the output code, please use the -u parameter to update the snapshots for these cases.
| Entry File | StatsAPI.test.js |
|---|---|
| Case Directory | tests/statsAPICases |
| Output Directory | None |
| Default Configuration | None |
| Run Output | No |
This test uses tests/rspack-test/fixtures as the source code for the build, so the test case is written as a single file. Its structure is as follows:
type TStatsAPICaseConfig = {
description: string, // Case description
options?: (context: ITestContext) => TCompilerOptions<T>, // Case build configuration
build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>, // Case build method
check?: (stats: TCompilerStats<T>, compiler: TCompiler<T>) => Promise<void>, // Function to check the stats for the case
};
/** @type {import('../..').TStatsAPICaseConfig} */
module.exports = {
// ...
};| Entry File | Diagnostics.test.js |
|---|---|
| Case Directory | tests/diagnosticsCases |
| Output Directory | tests/js/diagnostics |
| Default Configuration | DiagnosticProcessor |
| Run Output | No |
This test case is similar to a typical rspack project and can specify build configurations by adding a rspack.config.js. Additionally, it will add a stats.err file in the case directory to store snapshots of warnings/errors. To refresh, use the -u parameter.
| Entry File | Hash.test.js |
|---|---|
| Case Directory | tests/hashCases |
| Output Directory | None |
| Default Configuration | HashProcessor |
| Run Output | No |
This test case is similar to a typical rspack project, but it will add a test.config.js file in the case directory and specify a validate() method to check the hash information in the stats object after the build is complete:
type THashCaseConfig = {
validate?: (stats: TCompilerStats<T>) => void,
};
/** @type {import('../..').THashCaseConfig} */
module.exports = {
// ...
};| Entry File | Compiler.test.js |
|---|---|
| Case Directory | tests/compilerCases |
| Output Directory | None |
| Default Configuration | None |
| Run Output | No |
This test uses tests/rspack-test/fixtures as the source code for the build, so the test case is written as a single file. Its structure is as follows:
interface TCompilerCaseConfig {
description: string; // Description of the test case
options?: (context: ITestContext) => TCompilerOptions<T>; // Test case build configuration
compiler?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // How the compiler is created for the test case
build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // Build method for the test case
check?: (
context: ITestContext,
compiler: TCompiler<T>,
stats: TCompilerStats<T>,
) => Promise<void>; // Check function for the test case
}
/** @type {import('@rspack/core').TCompilerCaseConfig} */
module.exports = {
// ...
};| Entry File | Defaults.test.js |
|---|---|
| Case Directory | tests/defaultCases |
| Output Directory | None |
| Default Configuration | None |
| Run Output | No |
This test does not execute real builds; it only generates build configurations and observes the differences from the default configuration. The basic default configuration will be snapshot and stored in tests/rspack-test/__snapshots__/Defaults.test.js.snap.
This test uses tests/rspack-test/fixtures as the source code for the build, so the test case is written as a single file. Its structure is as follows:
interface TDefaultsCaseConfig {
description: string; // Description of the test case
cwd?: string; // process.cwd for generating the build configuration of the test case, default is the `tests/rspack-test` directory
options?: (context: ITestContext) => TCompilerOptions<ECompilerType.Rspack>; // Test case build configuration
diff: (
diff: Assertion<Diff>,
defaults: Assertion<TCompilerOptions<ECompilerType.Rspack>>,
) => Promise<void>; // Differences from the default configuration
}
/** @type {import('../..').TDefaultsCaseConfig} */
module.exports = {
// ...
};The details for the Error test are as follows:
| Entry File | Error.test.js |
|---|---|
| Case Directory | tests/errorCases |
| Output Directory | None |
| Default Configuration | ErrorProcessor |
| Run Output | No |
This test uses tests/rspack-test/fixtures as the source code for the build, so the test case is written as a single file. Its structure is as follows:
interface TErrorCaseConfig {
description: string; // Description of the test case
options?: (
options: TCompilerOptions<T>,
context: ITestContext,
) => TCompilerOptions<T>; // Test case configuration
build?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // Test case build method
check?: (stats: TStatsDiagnostics) => Promise<void>; // Function to check the test case
}
/** @type {import('../..').TErrorCaseConfig} */
module.exports = {
// ...
};| Entry File | Hook.test.js |
|---|---|
| Case Directory | tests/hookCases |
| Output Directory | None |
| Default Configuration | HookProcessor |
| Run Output | No |
This test records the input and output of the hook and stores it in the snapshot hooks.snap.txt. The snapshot of the final product code is stored in output.snap.txt.
This test uses tests/rspack-test/fixtures as the source code for the build, so the test case is written as a single file. Its structure is as follows:
interface THookCaseConfig {
description: string; // Description of the test case
options?: (
options: TCompilerOptions<T>,
context: ITestContext,
) => TCompilerOptions<T>; // Test case configuration
compiler?: (context: ITestContext, compiler: TCompiler<T>) => Promise<void>; // Callback after creating the compiler instance
check?: (context: ITestContext) => Promise<void>; // Callback after the build is completed
}
/** @type {import("@rspack/test-tools").THookCaseConfig} */
module.exports = {
// ...
};| Entry File | TreeShaking.test.js |
|---|---|
| Case Directory | tests/treeShakingCases |
| Output Directory | tests/js/treeShaking |
| Default Configuration | TreeShakingProcessor |
| Run Output | No |
In this test case, the configuration is similar to a regular rspack project. You can specify the build configuration by adding a rspack.config.js, but the final product is snapshot and stored in __snapshots__/treeshaking.snap.txt.
| Entry File | Builtin.test.js |
|---|---|
| Case Directory | tests/builtinCases |
| Output Directory | tests/js/builtin |
| Default Configuration | BuiltinProcessor |
| Run Output | No |
This test case is similar to a regular rspack project, and you can specify the build configuration by adding a rspack.config.js. However, depending on the directory, different snapshots of the products will be generated and stored in __snapshots__/output.snap.txt:
.css extension.css and .js extensions.html extension.js extension