close

Migrating from Jest

Rstest is designed to be Jest-compatible, making migration from Jest projects straightforward. Here's how to migrate your Jest project to Rstest:

Using Agent Skills

If you are using a Coding Agent that supports Skills, install the migrate-to-rstest skill to help with the upgrade process from Jest to Rstest.

npx skills add rstackjs/agent-skills --skill migrate-to-rstest

After installation, let the Coding Agent guide you through the upgrade.

Installation and setup

First, you need to install Rstest as a development 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 jest. For example:

"scripts": {
-  "test": "jest"
+  "test": "rstest"
}

CLI option mappings

Some Jest CLI flags map directly to Rstest, while others move into config. Use this table for the common option differences you are likely to hit during migration:

Jest CLI optionRstest equivalentNotes
jestrstest
jest --watchrstest --watch or rstest watch
jest --watchAllrstest --watch or rstest watchRstest does not distinguish between --watch and --watchAll.
jest --runInBandrstest --pool.maxWorkers 1
jest --maxWorkers=50% or jest -w 50%rstest --pool.maxWorkers 50%Rstest's -w means --watch, not --maxWorkers.
jest --selectProjects apprstest --project app
jest --env=jsdomrstest --testEnvironment jsdom
jest --coveragerstest --coverageAlso install @rstest/coverage-istanbul.
jest --coverageDirectory=coveragecoverage.reportsDirectory in rstest.config.ts
jest --coverageProvider=v8RemoveOnly 'istanbul' is supported — see the coverageProvider config row.

Configuration migration

Update your Jest config file (e.g., jest.config.js or jest.config.ts) to a rstest.config.ts file:

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

export default defineConfig({
  // Fill in by mapping fields from your jest.config.js — see the table below.
});

Jest configuration mappings

When migrating, walk through every field in your jest.config.js and match it against the table below — map it, restructure it, or drop it. Fields not listed here may not map 1:1; verify against the Rstest config reference before dropping them silently.

Jest configurationRstest equivalentNotes
preset (e.g. 'ts-jest')RemoveRstest uses swc by default; ts-jest is not needed.
transformRemoveswc transforms .ts / .tsx / .js / .jsx out of the box. For custom Babel, see Code transformation.
testEnvironmenttestEnvironment
testEnvironmentOptionstestEnvironment.optionsFold into the object form: testEnvironment: { name: 'jsdom', options: { ... } }.
testRegexinclude
testMatchincludeStrip the <rootDir>/ prefix from each pattern.
testPathIgnorePatternsexcludeWrap bare /name/ with **, e.g. '/node_modules/''**/node_modules/**'.
transformIgnorePatternsoutput.bundleDependenciesDefault differs by testEnvironment. For 'node': Rstest externalizes node_modules — drop most entries; for ESM packages where Jest needed an exception (e.g. 'node_modules/(?!(lodash-es)/)'), use output.bundleDependencies: ['lodash-es'] to bundle them through swc. For 'jsdom' / 'happy-dom': node_modules is bundled by default — transformIgnorePatterns usually has no equivalent and can be dropped entirely.
displayNamename
rootDirroot
setupFilesAfterEnvsetupFilesRstest's setupFiles runs after the test framework is registered — equivalent to Jest's setupFilesAfterEnv (not Jest's plain setupFiles). Strip the <rootDir>/ prefix. Merge any Jest setupFiles entries here too; there is no separate "before framework" hook.
globalSetupglobalSetupRstest calls setup with no arguments — if your Jest setup reads (globalConfig, projectConfig), rewrite without them. As in Jest, process.env mutations made here propagate to every test worker.
globalTeardownglobalSetupNo separate field. Rewrite the file to globalSetup's format (see above) — a bare export default async function teardown() from Jest would run at setup, not after tests.
verbosereporters: 'verbose'No verbose boolean — use the verbose reporter.
reportersreportersString values must be built-in names. For third-party reporters (e.g. jest-junit), import the reporter class and pass the instance.
injectGlobalsglobalsJest defaults to true; Rstest's globals defaults to false. If your tests rely on bare describe / test / expect without imports, set globals: true and add @rstest/core/globals to compilerOptions.types in tsconfig.json.
moduleNameMapperresolve.aliasresolve.alias is string-prefix only. Rewrite prefix mappings: '^@/(.*)$': '<rootDir>/src/$1'resolve: { alias: { '@': './src' } }. For TypeScript projects, prefer compilerOptions.paths in tsconfig.json — Rstest reads it automatically. Regex/asset stubs (e.g. '\\.(css|svg)$': 'identity-obj-proxy') have no direct equivalent; most are unnecessary because Rstest handles CSS/assets natively.
maxWorkerspool.maxWorkers
testTimeouttestTimeoutFor per-test overrides, replace jest.setTimeout(n) with rstest.setConfig({ testTimeout: n }).
slowTestThresholdslowTestThresholdJest measures in seconds (default 5); Rstest measures in milliseconds (default 300). Multiply Jest values by 1000.
fakeTimersrstest.useFakeTimersNo config field. Call rstest.useFakeTimers(opts) inside a setupFiles module or per-test; pair with rstest.useRealTimers() to restore.
bailbail
clearMocksclearMocks
resetMocksresetMocks
restoreMocksrestoreMocks
snapshotFormatsnapshotFormat
snapshotResolverresolveSnapshotPathRstest takes a function, not a module path.
snapshotSerializersexpect.addSnapshotSerializerNo config field. In a setupFiles module, import each serializer and call expect.addSnapshotSerializer(serializer).
cacheDirectoryRemoveNot supported. Rstest manages build caching internally via Rsbuild.
collectCoveragecoverage.enabled
collectCoverageFromcoverage.include
coverageDirectorycoverage.reportsDirectory
coverageProviderRemoveRstest only supports 'istanbul' (the default), so there is nothing to configure — drop the field. 'v8' / 'babel' providers are not supported.
coveragePathIgnorePatternscoverage.exclude
coverageThresholdcoverage.thresholds
projectsprojectsJest's inline project shape differs — review before porting.

For more details, please refer to the Configuration section.

Inject globals

Rstest does not mount the test APIs (e.g., describe, expect, it, test) to the global object by default, which is different from Jest.

If you want to continue using the global test APIs, you can enable the globals option in your rstest.config.ts file:

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

export default defineConfig({
  globals: true,
});

To enable TypeScript to properly recognize the global APIs, add the @rstest/core/globals type declaration in your tsconfig.json:

tsconfig.json
{
  "compilerOptions": {
    "types": ["@rstest/core/globals"]
  }
}

Code transformation

Rstest uses swc for code transformation by default, which is different from Jest's babel-jest. Most of the time, you don't need to change anything. And you can configure your swc options through tools.swc.

export default {
-  transform: {
-    '^.+\\.(t|j)sx?$': ['@swc/jest', {}],
-  },
+  tools: {
+    swc: {}
+  }
}

However, if you have custom Babel configurations or use specific Babel plugins/presets, you can add Rsbuild's Babel Plugin:

rstest.config.ts
import { pluginBabel } from '@rsbuild/plugin-babel';
import { defineConfig } from '@rstest/core';

export default defineConfig({
  plugins: [pluginBabel()],
});

Update test API

Test API

Your existing Jest test files should work with minimal changes since Rstest provides Jest-compatible APIs. Simply update your imports from Jest to Rstest:

- import { describe, expect, it, test } from '@jest/globals';
+ import { describe, expect, it, test } from '@rstest/core';

Rstest provides a rstest API that you can use to access Rstest's utilities, such as rstest.fn() and rstest.mock(). Just like Jest's jest.fn() and jest.mock(). More utilities can be found in the Rstest APIs.

- const fn = jest.fn();
+ const fn = rstest.fn();

fn.mockResolvedValue('foo');

Done callback

The done callback is not supported in Rstest. Instead, you can return a Promise or use async/await for asynchronous tests.

- test('async test with done', (done) => {
+ test('async test with done', () => new Promise(done => {
  // ...
  done();
- });
+ }));

If you need to handle errors, you can modify it as follows:

- test('async test with done', (done) => {
+ test('async test with done', () => new Promise((resolve, reject) => {
+   const done = err => (err ? reject(err) : resolve());
  // ...
  done(error);
- });
+ }));

Hooks

The return functions of the beforeEach and beforeAll hooks in Rstest are used to perform cleaning work after testing.

- beforeEach(() => doSomething());
+ beforeEach(() => { doSomething() });

Timeout

If you used jest.setTimeout() to set the timeout for a test, you can use rstest.setConfig() instead.

- jest.setTimeout(5_000)
+ rstest.setConfig({ testTimeout: 5_000 })

Snapshot format

Rstest snapshots use a different key format than Jest. Existing Jest snapshot files will not match Rstest's generated keys on the first run — the snapshot bodies are unchanged, only the key formatting differs.

Key separator change

Jest joins the suite name, test name, and snapshot label with :. Rstest uses >:

- overlay should not show a warning when "client.overlay.warnings" is "false": page html 1
+ overlay > should not show a warning when "client.overlay.warnings" is "false" > page html 1

For the example above, the parts are:

  1. overlay — suite name (describe(...)).
  2. should not show a warning when "client.overlay.warnings" is "false" — test name (it(...) / test(...)).
  3. page html — snapshot label from .toMatchSnapshot('page html').
  4. 1 — snapshot index for that label in the test.

Updating snapshots

rstest -u re-records snapshots in Rstest's key format:

rstest -u                # update everything
rstest overlay -u        # update a filtered scope

Reviewing the diff

Most snapshot churn after migration is non-functional:

  • Separator-only key renames are formatting changes, not behavior changes.
  • Key-order churn without any body difference is also formatting-only.
  • Body content changes under the same key are the only signal of a real behavior change.

ESM and CJS

Rstest supports ESM by default. If your project is using ESM, you don't need to perform any extra configuration, such as setting NODE_OPTIONS=--experimental-vm-modules.

If your project is using CommonJS, Rstest still works, but it is recommended to migrate to ESM for better performance and future compatibility.

ESM vs CJS mocking

In Rstest, rstest.mock() targets ESM entries used by import, while rstest.mockRequire() targets CJS entries used by require().

For code that uses require(), rstest.mockRequire() targets the CJS entry:

// Mocking a CJS module
rstest.mockRequire('./math.cjs', () => ({
  sum: (a, b) => a + b + 100,
}));