从 Vitest 迁移
如果你正在使用 Rstack 工具链(Rsbuild / Rslib / Rspack 等),迁移到 Rstest 可以带来更一致的开发体验。
使用 Agent Skills
如果你在使用支持 Skills 的 Coding Agent,可以安装 migrate-to-rstest 技能来辅助完成从 Vitest 到 Rstest 的迁移。
安装后,让 Coding Agent 协助完成升级即可。
安装依赖
首先,你需要安装 Rstest 依赖。
接下来,更新 package.json 中的测试脚本,使用 rstest 替代 vitest。例如:
rstest 没有 --run 参数。直接运行 rstest 就会执行一次测试并退出;如果你想使用 watch 模式,可以加上 --watch:
CLI 参数映射
Vitest 的一部分 CLI 参数可以直接映射到 Rstest,另一部分则需要调整写法。迁移时,最常遇到的差异可以参考下表:
配置迁移
将你的 Vitest 配置文件(例如 vite.config.ts 或 vitest.config.ts)迁移为 rstest.config.ts:
Helper 映射
Vitest 配置文件中使用的 helper 可以对应到 @rstest/core 导出的同类方法:
Vitest 配置映射
迁移配置时,重点关注这两点:
- 移除
test字段,将其内部配置提升到顶层。 - 一些字段名的调整(例如
test.environment→testEnvironment)。
请遍历 test 下的每一个字段,对照下表进行提升、重命名或删除。表中未列出的字段未必能 1:1 映射,直接删除前请先对照 Rstest 配置参考 确认。
| Vitest(test 下) | Rstest(顶层) | 说明 |
| ----------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| environment | testEnvironment | 将 test.environmentOptions 合并到对象形式:testEnvironment: { name: 'jsdom', options: { ... } }。不支持自定义 environment 包。 |
| include / exclude | include / exclude | |
| includeSource | includeSource | |
| setupFiles | setupFiles | |
| globalSetup | globalSetup | Rstest 调用 setup 时不传参数 —— 如果你的 Vitest setup 读取了 TestProject 参数(如 provide、onTestsRerun),迁移时需重写。Vitest 的 provide / inject 没有直接等价形式 —— 在 setup 里修改 process.env(Rstest 会在 setup 结束后快照并注入每个 worker),或使用 env 配置字段传递静态值。 |
| globals | globals | |
| name | name | |
| root | root | |
| env | env | |
| alias | resolve.alias | Rstest 下不是 test.* 字段 —— 迁到顶层 resolve.alias。 |
| passWithNoTests | passWithNoTests | |
| isolate | isolate | |
| testTimeout / hookTimeout | testTimeout / hookTimeout | |
| teardownTimeout | 移除 | 没有对等字段 —— Vitest 的 teardownTimeout 是 shutdown 等待超时,与 Rstest 的 hookTimeout(生命周期 hook)无关。 |
| slowTestThreshold | slowTestThreshold | |
| maxConcurrency | maxConcurrency | |
| retry | retry | |
| bail | bail | |
| clearMocks | clearMocks | |
| mockReset | resetMocks | |
| restoreMocks | restoreMocks | |
| poolOptions.forks.maxForks | pool.maxWorkers | Rstest 把 poolOptions 摊平到顶层 pool:poolOptions.forks.maxForks → pool.maxWorkers、minForks → pool.minWorkers、execArgv → pool.execArgv。只支持 forks pool —— pool: 'threads' \| 'vmThreads' \| 'vmForks' 这些配置会被丢弃(行为回退到 forks)。Vitest 4 的顶层 test.maxWorkers / test.minWorkers 也映射到 pool.maxWorkers / pool.minWorkers。 |
| coverage | coverage | 仅支持 provider: 'istanbul' —— 把 dev 依赖从 @vitest/coverage-v8 换成 @rstest/coverage-istanbul(去掉 'v8' / 'custom')。把 coverage.reporter 改为 coverage.reporters(单数写法会被静默忽略)。下列子字段 1:1 对应:include、exclude、reportsDirectory、thresholds。V8 专用字段(all、skipFull、thresholdAutoUpdate、processingConcurrency、customProviderModule、watermarks、ignoreClassMethods 等)没有对等字段。 |
| reporters | reporters | Vitest 独有(需替换或丢弃):tap、tap-flat、html、tree、hanging-process。字符串必须是内建 reporter 名称;第三方 reporter 需 import 类并传入实例。 |
| outputFile | reporter options | 没有顶层字段。junit / json 用 reporter 元组传 outputPath:['junit', { outputPath: '...' }];blob 用 { outputDir: '...' };其他 reporter 不接受输出路径。对象形 { junit: 'a.xml', json: 'a.json' } 展开为每个 reporter 一条 tuple。 |
| snapshotFormat | snapshotFormat | |
| resolveSnapshotPath | resolveSnapshotPath | Rstest 的 callback 签名是 (testPath, snapExtension) => string,没有 Vitest 3+ 的第三个 context 参数。 |
| snapshotSerializers | expect.addSnapshotSerializer | 没有配置字段。在 setupFiles 模块里 import 每个 serializer,并调用 expect.addSnapshotSerializer(serializer)。 |
| projects | projects | |
| logHeapUsage | logHeapUsage | |
| includeTaskLocation | includeTaskLocation | |
| silent | silent | 与 Vitest 一样支持 boolean | 'passed-only',用于控制被拦截的测试 console 输出。 |
| printConsoleTrace | printConsoleTrace | |
| unstubGlobals | unstubGlobals | |
| unstubEnvs | unstubEnvs | |
| chaiConfig | chaiConfig | |
编译配置
Rstest 使用 Rsbuild 作为默认测试编译工具,而不是 Vite。你可以在 Build Configurations 查看全部编译配置项。
大部分项目中,主要的编译侧变化如下:
- 使用
source.define替代define。 - 使用
output.externals替代ssr.external。 - 使用 Rsbuild 插件替代 Vite 插件。
如果你使用的是 Rslib 或 Rsbuild,也可以直接复用对应配置:
- Rslib 项目(存在
rslib.config.*)使用@rstest/adapter-rslib,并在extends中配置withRslibConfig()(参考 Rslib 集成文档)。 - Rsbuild 项目(存在
rsbuild.config.*)使用@rstest/adapter-rsbuild,并在extends中配置withRsbuildConfig()(参考 Rsbuild 集成文档)。
更新测试 API
测试 API
Rstest 提供了与 Vitest 兼容的 API,已有的 Vitest 测试文件通常只需要极少改动。将 vitest 的导入替换为 @rstest/core,并把 vi / vitest 工具 API 替换为对应的 rs / rstest:
完整工具 API 请参考 Rstest APIs。
全局 API
当启用 globals: true 时,Vitest 会把 vi 和 vitest 挂在全局对象上。在 Rstest 中,建议按以下顺序映射:
vi.<api>→rs.<api>vitest.<api>→rstest.<api>
rs 和 rstest 是等价的全局别名,但迁移时按这个一一对应关系更容易阅读和排查。
从 @rstest/core 导入 API 时,统一使用 import style 的 rs.<api> 更一致,避免在同一文件里与 global style 混用。
Setup adapter
有些 setup adapter 是 Vitest 专用的。例如 @testing-library/jest-dom/vitest 面向 Vitest;在 Rstest 中通过 expect.extend 直接注册 matcher。
路径解析
在某些 transform/runtime 模式下,new URL(..., import.meta.url) 可能会在 setup 或 helper 文件中失效。
如果你看到 Cannot find module './' 或 Cannot find module '..' 这类路径错误,建议改用 Node 风格的 __dirname 路径解析:
自动模拟模块
在 Vitest 中,调用 vi.mock() 且只传模块路径时,会先尝试从对应 __mocks__ 目录加载手动 mock;如果没找到,再自动 mock 整个模块,把导出替换为空 mock 函数。
Rstest 的行为不同。调用 rs.mock() 且只传模块路径时,只会查找 __mocks__,若未找到会报错。启用自动 mock 需要显式传入 { mock: true }。
Mock 异步模块
当你需要 mock 模块返回值时,Rstest 不支持返回异步函数。
作为替代,Rstest 提供了同步 importActual 能力,你可以通过静态 import 导入未 mock 的真实实现:
mock factory 是 hoisted 执行的;依赖同模块中后初始化的变量会触发初始化顺序错误。共享值可放到 hoisted initializer(例如 rs.hoisted(...))中规避。
Snapshot
Vitest 和 Rstest 使用相同的 snapshot key 格式和 body 序列化方式。原有的 __snapshots__/*.snap 文件可以被 Rstest 原样读取;在 Vitest 下能通过的测试,到 Rstest 下也能通过,不需要重录。两者只有文件 header 行不同:
执行 rstest -u 会把 header 规整为 Rstest 形式,snapshot body 保持 byte-identical。