Transform a Jest test into a Vitest test
Migrate a Jest test file (e.g. packages/core/src/foo.test.ts) into a Vitest test file (packages/core/vitest/foo.test.ts). The reference pair for this transformation is packages/core/src/matterbridgeFactory.test.ts (Jest) vs packages/core/vitest/matterbridgeFactory.test.ts (Vitest) — when in doubt, diff those two files.
Ground rules
- Create the new file under
packages/<pkg>/vitest/. The root vite.config.ts only picks up **/vitest/**/*.{spec,test}.{ts,mts,cts}. Do NOT delete the Jest original unless explicitly asked.
- Test bodies, assertions,
describe/test names, MATTER_PORT, MATTER_CREATE_ONLY, and hook timeouts (e.g. 30000) stay unchanged. Only the harness around them changes.
- Each test file must keep a unique
MATTER_PORT. If creating a brand-new file, pick an unused port (check existing tests).
- Vitest runs with
globals: true: describe, test, expect, beforeAll, beforeEach, afterAll, and vi are globals. Do not import them from 'vitest' and do not import '@jest/globals'.
Example file to transform
- packages\core\vitest\matterbridgeFactory.test.ts
Step-by-step transformation
1. Header block
Remove the Jest-only boilerplate. setupTest(NAME) from @matterbridge/vitest-utils sets HOMEDIR (to .cache/vitest/<NAME>) and process.argv itself.
// Jest (remove)
const HOMEDIR = path.join('.cache', 'jest', NAME);
process.argv = ['node', 'matterbridge.js', '--frontend', '0', '--port', MATTER_PORT.toString(), '--homedir', HOMEDIR, ...];
import path from 'node:path';
Keep only:
const NAME = 'Factory';
const MATTER_PORT = 11280;
const MATTER_CREATE_ONLY = true;
Update the first-line path comment to match the new file location.
2. Imports
| Jest |
Vitest |
import { jest } from '@jest/globals'; |
delete (use global vi) |
import { loggerLogSpy, setupTest } from './jestutils/jestSetupTest.js'; |
import { loggerLogSpy, setupTest } from '@matterbridge/vitest-utils'; |
import { addDevice } from './jestutils/jestMatterTest.js'; |
import { addDevice, aggregator, createServerNode, createTestEnvironment, destroyTestEnvironment, flushServerNode, startServerNode, stopServerNode } from '@matterbridge/vitest-utils/matter'; |
import { ... } from './jestutils/jestMatterbridgeTest.js'; |
covered by @matterbridge/vitest-utils/matter (see lifecycle below) |
import { foo } from './foo.js'; (source under test) |
import { foo } from '../src/foo.js'; |
import { Endpoint, ServerNode } from '@matter/node'; + AggregatorEndpoint (only used for the local server/aggregator types) |
delete — server and aggregator are imported live bindings from @matterbridge/vitest-utils/matter |
Other imports (@matter/types/..., node-ansi-logger, etc.) stay as-is. Keep ESLint import group ordering: node builtins, externals (including @matterbridge/vitest-utils*), then relative ../src/....
All spies (loggerLogSpy, loggerDebugSpy, consoleLogSpy, ...) and setDebug come from @matterbridge/vitest-utils. Matter helpers (addDevice, deleteDevice, server, aggregator, getMatterbridge, addMatterbridge, ...) come from @matterbridge/vitest-utils/matter.
3. Matter environment lifecycle
Jest pattern:
let server: ServerNode<ServerNode.RootEndpoint>;
let aggregator: Endpoint<AggregatorEndpoint>;
beforeAll(async () => {
await createMatterbridgeEnvironment();
[server, aggregator] = await startMatterbridgeEnvironment(MATTER_PORT, MATTER_CREATE_ONLY);
}, 30000);
afterAll(async () => {
await stopMatterbridgeEnvironment(MATTER_CREATE_ONLY);
await destroyMatterbridgeEnvironment();
jest.restoreAllMocks();
}, 30000);
Vitest pattern (no local server/aggregator declarations — they are imported):
beforeAll(async () => {
// Setup the Matter test environment
await createTestEnvironment();
// Create the server node and aggregator
await createServerNode(MATTER_PORT);
// Start the server node if not in create-only mode
if (!MATTER_CREATE_ONLY) await startServerNode();
}, 30000);
afterAll(async () => {
// Stop or flush the server node depending on the create-only mode
if (MATTER_CREATE_ONLY) await flushServerNode();
else await stopServerNode();
// Destroy the Matter test environment
await destroyTestEnvironment();
// Restore all mocks
vi.restoreAllMocks();
}, 30000);
Tests that don't use a Matter server node at all keep just setupTest(NAME, false) and skip the lifecycle entirely.
4. Mock API renames
jest.clearAllMocks() → vi.clearAllMocks() (keep the beforeEach — the config has clearMocks: false).
jest.restoreAllMocks() → vi.restoreAllMocks() (config has restoreMocks: false).
jest.spyOn(...) → vi.spyOn(...), jest.fn() → vi.fn().
jest.unstable_mockModule('mod', factory) + dynamic import() → vi.mock('mod', factory) (hoisted; static imports work) or vi.doMock + dynamic import for the unhoisted equivalent.
- Types:
jest.Mock / jest.SpiedFunction → Mock / MockInstance from 'vitest' (type-only import is allowed).
5. Validate
- Typecheck uses the root
tsconfig.vitest.json (includes **/vitest/**/*.test.ts).
- Run the single file from the repo root:
npm run test:vitest -- packages/core/vitest/foo.test.ts
- Coverage variant:
npm run test:vitest:coverage -- packages/core/vitest/foo.test.ts
- The whole converted file must pass, not isolated tests — these are multi-step flow suites.
Checklist before finishing
- File under
packages/<pkg>/vitest/, source imports use ../src/.
- No
@jest/globals, no jest. references, no path/HOMEDIR/process.argv boilerplate left.
- No
import { describe, ... } from 'vitest' (globals are on); type-only vitest imports are fine.
server/aggregator come from @matterbridge/vitest-utils/matter, not local variables.
- Unique
MATTER_PORT; timeouts preserved; test bodies untouched.
npm run test:vitest -- <file> passes.
Source: Luligu/matterbridge — distributed by TomeVault.
1---2name: jest-to-vitest3description: Transform a Jest test file from packages/<pkg>/src into a Vitest test file under packages/<pkg>/vitest, following the Matterbridge migration pattern (vitest-utils package, vi globals, new Matter server lifecycle). Use when asked to migrate, convert, or transform a Jest test to Vitest. Use when this capability is needed.4---56# Transform a Jest test into a Vitest test78Migrate a Jest test file (e.g. `packages/core/src/foo.test.ts`) into a Vitest test file (`packages/core/vitest/foo.test.ts`). The reference pair for this transformation is `packages/core/src/matterbridgeFactory.test.ts` (Jest) vs `packages/core/vitest/matterbridgeFactory.test.ts` (Vitest) — when in doubt, diff those two files.910## Ground rules1112- Create the new file under `packages/<pkg>/vitest/`. The root `vite.config.ts` only picks up `**/vitest/**/*.{spec,test}.{ts,mts,cts}`. Do NOT delete the Jest original unless explicitly asked.13- Test bodies, assertions, `describe`/`test` names, `MATTER_PORT`, `MATTER_CREATE_ONLY`, and hook timeouts (e.g. `30000`) stay unchanged. Only the harness around them changes.14- Each test file must keep a unique `MATTER_PORT`. If creating a brand-new file, pick an unused port (check existing tests).15- Vitest runs with `globals: true`: `describe`, `test`, `expect`, `beforeAll`, `beforeEach`, `afterAll`, and `vi` are globals. Do not import them from `'vitest'` and do not import `'@jest/globals'`.1617## Example file to transform1819- packages\core\vitest\matterbridgeFactory.test.ts2021## Step-by-step transformation2223### 1. Header block2425Remove the Jest-only boilerplate. `setupTest(NAME)` from `@matterbridge/vitest-utils` sets `HOMEDIR` (to `.cache/vitest/<NAME>`) and `process.argv` itself.2627```ts28// Jest (remove)29const HOMEDIR = path.join('.cache', 'jest', NAME);30process.argv = ['node', 'matterbridge.js', '--frontend', '0', '--port', MATTER_PORT.toString(), '--homedir', HOMEDIR, ...];31import path from 'node:path';32```3334Keep only:3536```ts37const NAME = 'Factory';38const MATTER_PORT = 11280;39const MATTER_CREATE_ONLY = true;40```4142Update the first-line path comment to match the new file location.4344### 2. Imports4546| Jest | Vitest |47| ----------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |48| `import { jest } from '@jest/globals';` | delete (use global `vi`) |49| `import { loggerLogSpy, setupTest } from './jestutils/jestSetupTest.js';` | `import { loggerLogSpy, setupTest } from '@matterbridge/vitest-utils';` |50| `import { addDevice } from './jestutils/jestMatterTest.js';` | `import { addDevice, aggregator, createServerNode, createTestEnvironment, destroyTestEnvironment, flushServerNode, startServerNode, stopServerNode } from '@matterbridge/vitest-utils/matter';` |51| `import { ... } from './jestutils/jestMatterbridgeTest.js';` | covered by `@matterbridge/vitest-utils/matter` (see lifecycle below) |52| `import { foo } from './foo.js';` (source under test) | `import { foo } from '../src/foo.js';` |53| `import { Endpoint, ServerNode } from '@matter/node';` + `AggregatorEndpoint` (only used for the local `server`/`aggregator` types) | delete — `server` and `aggregator` are imported live bindings from `@matterbridge/vitest-utils/matter` |5455Other imports (`@matter/types/...`, `node-ansi-logger`, etc.) stay as-is. Keep ESLint import group ordering: node builtins, externals (including `@matterbridge/vitest-utils*`), then relative `../src/...`.5657All spies (`loggerLogSpy`, `loggerDebugSpy`, `consoleLogSpy`, ...) and `setDebug` come from `@matterbridge/vitest-utils`. Matter helpers (`addDevice`, `deleteDevice`, `server`, `aggregator`, `getMatterbridge`, `addMatterbridge`, ...) come from `@matterbridge/vitest-utils/matter`.5859### 3. Matter environment lifecycle6061Jest pattern:6263```ts64let server: ServerNode<ServerNode.RootEndpoint>;65let aggregator: Endpoint<AggregatorEndpoint>;6667beforeAll(async () => {68 await createMatterbridgeEnvironment();69 [server, aggregator] = await startMatterbridgeEnvironment(MATTER_PORT, MATTER_CREATE_ONLY);70}, 30000);7172afterAll(async () => {73 await stopMatterbridgeEnvironment(MATTER_CREATE_ONLY);74 await destroyMatterbridgeEnvironment();75 jest.restoreAllMocks();76}, 30000);77```7879Vitest pattern (no local `server`/`aggregator` declarations — they are imported):8081```ts82beforeAll(async () => {83 // Setup the Matter test environment84 await createTestEnvironment();8586 // Create the server node and aggregator87 await createServerNode(MATTER_PORT);8889 // Start the server node if not in create-only mode90 if (!MATTER_CREATE_ONLY) await startServerNode();91}, 30000);9293afterAll(async () => {94 // Stop or flush the server node depending on the create-only mode95 if (MATTER_CREATE_ONLY) await flushServerNode();96 else await stopServerNode();9798 // Destroy the Matter test environment99 await destroyTestEnvironment();100101 // Restore all mocks102 vi.restoreAllMocks();103}, 30000);104```105106Tests that don't use a Matter server node at all keep just `setupTest(NAME, false)` and skip the lifecycle entirely.107108### 4. Mock API renames109110- `jest.clearAllMocks()` → `vi.clearAllMocks()` (keep the `beforeEach` — the config has `clearMocks: false`).111- `jest.restoreAllMocks()` → `vi.restoreAllMocks()` (config has `restoreMocks: false`).112- `jest.spyOn(...)` → `vi.spyOn(...)`, `jest.fn()` → `vi.fn()`.113- `jest.unstable_mockModule('mod', factory)` + dynamic `import()` → `vi.mock('mod', factory)` (hoisted; static imports work) or `vi.doMock` + dynamic import for the unhoisted equivalent.114- Types: `jest.Mock` / `jest.SpiedFunction` → `Mock` / `MockInstance` from `'vitest'` (type-only import is allowed).115116### 5. Validate117118- Typecheck uses the root `tsconfig.vitest.json` (includes `**/vitest/**/*.test.ts`).119- Run the single file from the repo root: `npm run test:vitest -- packages/core/vitest/foo.test.ts`120- Coverage variant: `npm run test:vitest:coverage -- packages/core/vitest/foo.test.ts`121- The whole converted file must pass, not isolated tests — these are multi-step flow suites.122123## Checklist before finishing124125- File under `packages/<pkg>/vitest/`, source imports use `../src/`.126- No `@jest/globals`, no `jest.` references, no `path`/`HOMEDIR`/`process.argv` boilerplate left.127- No `import { describe, ... } from 'vitest'` (globals are on); type-only vitest imports are fine.128- `server`/`aggregator` come from `@matterbridge/vitest-utils/matter`, not local variables.129- Unique `MATTER_PORT`; timeouts preserved; test bodies untouched.130- `npm run test:vitest -- <file>` passes.131132---133> Source: [Luligu/matterbridge](https://github.com/Luligu/matterbridge) — distributed by [TomeVault](https://tomevault.io).134<!-- tomevault:4.0:skill_md:2026-06-28 -->