close

Migrating from Vitest

If you are using the Rstack toolchain (Rsbuild / Rslib / Rspack, etc.), migrating to Rstest gives you a more consistent development experience.

Install dependencies

First, you need to install the Rstest dependency.

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

Next, update the test script in your package.json to use rstest instead of vitest. For example:

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

rstest does not have a --run flag. Running rstest already executes tests once and exits. If you want watch mode, use --watch:

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

Configuration migration

Update your Vitest configuration file (e.g., vite.config.ts or vitest.config.ts) to an rstest.config.ts file:

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

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

Test configuration

Rstest test configuration is basically the same as Vitest.

When migrating config, keep these changes in mind:

  • Remove the test field and move its nested properties to the top level.
  • Rename keys when required (for example, test.environment -> testEnvironment).

You can view all available test configuration options through 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',
});

Build configuration

Rstest uses Rsbuild as the default test build tool instead of Vite. You can view all available build configuration options in Build Configurations.

In most projects, these are the key build-side changes:

  • Use source.define instead of define.
  • Use output.externals instead of ssr.external.
  • Use Rsbuild plugins instead of Vite plugins.
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,
+    },
+  },
});

If you are using Rslib or Rsbuild, you can directly use the corresponding adapter:

  • For Rslib projects (with rslib.config.*), use @rstest/adapter-rslib with extends: withRslibConfig() (see Rslib integration reference).
  • For Rsbuild projects (with rsbuild.config.*), use @rstest/adapter-rsbuild with extends: withRsbuildConfig() (see Rsbuild integration reference).

Replace test imports and APIs

For test APIs, migration is usually just two quick changes:

  1. Replace imports from vitest with imports from @rstest/core.
  2. Replace vi or vitest utility APIs with rs equivalents.
- 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()

For the full utility API list, see Rstest APIs.

Auto-mocking modules

In Vitest, calling vi.mock() with just the module path first attempts to load a manual mock from the corresponding __mocks__ directory. If no manual mock is found, it automatically mocks the module, replacing all its exports with empty mock functions.

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

// Looks for __mocks__/module.js first, then auto-mocks.
vi.mock('./module');

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

Rstest handles this differently. Calling rs.mock() with only the module path will only look for a mock in the __mocks__ directory and will throw an error if one isn't found. To achieve auto-mocking, you must explicitly pass the { mock: true } option.

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

- // Looks for __mocks__/module.js first, then auto-mocks.
- vi.mock('./module');
+ // Auto-mocks the module because { mock: true } is passed.
+ rs.mock('./module', { mock: true });

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

Mock async modules

When you need to mock a module's return value, Rstest does not support returning an async function.

As an alternative, Rstest provides synchronous importActual capability, allowing you to import the unmocked module implementation through static import statements:

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

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