close

从 Vitest 迁移

如果你正在使用 Rstack 工具链(Rsbuild / Rslib / Rspack 等),迁移到 Rstest 可以带来更一致的开发体验。

安装依赖

首先,你需要安装 Rstest 依赖。

npm
yarn
pnpm
bun
deno
npm add @rstest/core -D

接下来,更新 package.json 中的测试脚本,使用 rstest 替代 vitest。例如:

"scripts": {
-  "test": "vitest run" // 或 "vitest --run"
+  "test": "rstest"
}

rstest 没有 --run 参数。直接运行 rstest 就会执行一次测试并退出;如果你想使用 watch 模式,可以加上 --watch

"scripts": {
-  "test": "vitest"
+  "test": "rstest --watch"
}

配置迁移

将你的 Vitest 配置文件(例如 vite.config.tsvitest.config.ts)迁移为 rstest.config.ts

rstest.config.ts
import { defineConfig } from '@rstest/core';

export default defineConfig({
  include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
});

测试配置

Rstest 的测试配置和 Vitest 基本一致。

迁移配置时,重点关注这两点:

  • 移除 test 字段,将其内部配置提升到顶层。
  • 一些字段名的调整(例如 test.environment -> testEnvironment)。

你可以在 Test Configurations 查看全部测试配置项。

import { defineConfig } from '@rstest/core';

export default defineConfig({
-  test: {
-    include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
-    exclude: ['dist/**'],
-    setupFiles: ['./test/setup.ts'],
-    globals: true,
-    environment: 'node',
-  },
+  include: ['**/*.{test,spec}.?(c|m)[jt]s?(x)'],
+  exclude: ['dist/**'],
+  setupFiles: ['./test/setup.ts'],
+  globals: true,
+  testEnvironment: 'node',
});

编译配置

Rstest 使用 Rsbuild 作为默认测试编译工具,而不是 Vite。你可以在 Build Configurations 查看全部编译配置项。

大部分的配置可以直接迁移(如,resolve.aliasresolve.extensions 等),但需要注意以下几点:

  • 使用 source.define 替代 define
  • 使用 output.externals替代 ssr.external
  • 使用 Rsbuild 插件替代 Vite 插件。
import { defineConfig } from '@rstest/core';
- import react from '@vitejs/plugin-react'
+ import { pluginReact } from '@rsbuild/plugin-react';

export default defineConfig({
-  plugins: [react()],
-  define: {
-    __DEV__: true,
-  },
+  plugins: [pluginReact()],
+  source: {
+    define: {
+      __DEV__: true,
+    },
+  },
});

如果你使用的是 Rslib 或 Rsbuild,也可以直接复用对应配置:

  • Rslib 项目(存在 rslib.config.*)使用 @rstest/adapter-rslib,并在 extends 中配置 withRslibConfig()(参考 Rslib 集成文档)。
  • Rsbuild 项目(存在 rsbuild.config.*)使用 @rstest/adapter-rsbuild,并在 extends 中配置 withRsbuildConfig()(参考 Rsbuild 集成文档)。

替换测试导入与 API

测试 API 的迁移通常只需要两步:

  1. vitest 的导入替换为 @rstest/core
  2. vivitest 的工具 API 替换为 rs
- import { describe, expect, it, test, vi, type Mock } from 'vitest';
+ import { describe, expect, it, test, rs, type Mock } from '@rstest/core';
- vi.fn()
+ rs.fn()

- vi.mock('./foo')
+ rs.mock('./foo')

- vi.spyOn(console, 'error')
+ rs.spyOn(console, 'error')
- vitest.fn()
+ rs.fn()

完整工具 API 请参考 Rstest APIs

自动模拟模块

在 Vitest 中,调用 vi.mock() 且只传模块路径时,会先尝试从对应 __mocks__ 目录加载手动 mock;如果没找到,再自动 mock 整个模块,把导出替换为空 mock 函数。

// Vitest
import { vi, test, expect } from 'vitest';
import { someFunction } from './module';

// 优先查找 __mocks__/module.js,然后自动 mock。
vi.mock('./module');

test('should be mocked', () => {
  expect(vi.isMockFunction(someFunction)).toBe(true);
  someFunction(); // 返回 undefined
});

Rstest 的行为不同。调用 rs.mock() 且只传模块路径时,只会查找 __mocks__,若未找到会报错。要启用自动 mock,需要显式传入 { mock: true }

// Rstest
import { rs, test, expect } from '@rstest/core';
import { someFunction } from './module';

- // 优先查找 __mocks__/module.js,然后自动 mock。
- vi.mock('./module');
+ // 传入 { mock: true } 后会自动 mock 模块。
+ rs.mock('./module', { mock: true });

test('should be mocked', () => {
  expect(rs.isMockFunction(someFunction)).toBe(true);
  someFunction(); // 返回 undefined
});

Mock 异步模块

当你需要 mock 模块返回值时,Rstest 不支持返回异步函数。

作为替代,Rstest 提供了同步 importActual 能力,你可以通过静态 import 导入未 mock 的真实实现:

+ import * as apiActual from './api' with { rstest: 'importActual' };

- vi.mock('./api', async (importOriginal) => {
-   const original = await importOriginal();
-    return {
-     ...original,
-    fetchUser: rs.fn().mockResolvedValue({ id: 'mocked' }),
-  };
- });

+ rs.mock('./api', () => ({
+  ...apiActual,
  fetchUser: rs.fn().mockResolvedValue({ id: 'mocked' }),
}));