Migration Guide
Migrating to Vitest 4.0 | Migrating to Vitest 3.0
Migrating to Vitest 5.0
Work in progress
Vitest 5.0 is currently in beta. This section tracks breaking changes as they are merged and may change before the stable release.
Prerequisites
Vitest 5.0 requires Vite >= 6.4.0 and Node.js >= 22.12.0. Before proceeding with any other migration steps, ensure your environment meets these requirements. Running Vitest 5.0 on older versions of Vite or Node.js is not supported and may result in unexpected errors.
clearMocks is Enabled by Default
clearMocks now defaults to true. Vitest calls vi.clearAllMocks() before every test, resetting the mock.calls, mock.instances, mock.contexts and mock.results of every mock. Mock implementations are left intact, so this only affects the recorded history.
In practice this means a mock no longer carries calls from one test into the next:
import { expect, test, vi } from 'vitest'
const fn = vi.fn()
test('first', () => {
fn()
expect(fn).toHaveBeenCalledTimes(1)
})
test('second', () => {
fn()
// v4: the call from "first" was kept, so this was 2
expect(fn).toHaveBeenCalledTimes(2)
// v5: history is cleared before each test, so only this test's call counts
expect(fn).toHaveBeenCalledTimes(1)
})Tests that record calls outside of the test body (for example in a setup file, at the top level of a module, or in a beforeAll hook) are the most affected, because that history is cleared before the test that asserts on it runs.
To keep the previous behavior, set clearMocks back to false:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
clearMocks: false,
},
})Hoisted Mocking Calls Must Be at the Top Level
vi.mock, vi.unmock, and vi.hoisted are hoisted to the top of the file and run before any surrounding code. Calling them inside a function, block, or describe/test callback previously only logged a warning. Vitest 5.0 now throws, because the call does not execute where it is written:
describe('calculator', () => {
vi.mock('./calculator')
})
vi.mock('./calculator')
describe('calculator', () => {
// ...
})The error reports every offending call and its location:
1 call in "calculator.test.ts" was defined outside of the module's top level scope:
- vi.mock("./calculator") at calculator.test.ts:2:3
Although it appears nested, it will be hoisted and executed before anything in this file. Move it to the top level to reflect its actual execution order.The dynamic variants vi.doMock and vi.doUnmock are not hoisted and may still be called anywhere.
Automocked Modules Stay Automocked in the Browser
In browser mode, mock metadata is serialized between Vitest and the test iframe. An automocked module (a vi.mock call with no factory) was incorrectly restored as a spy on the other side, so its exports kept calling the real implementation instead of the auto-generated stubs.
Automocks are now restored as automocks. If a browser test relied on the original implementation running through an automocked module, its exports now return undefined by default. Pass { spy: true } to keep calling the real implementation while still tracking calls, or provide a factory with the behavior you need.
Benchmarking API Rewrite
The benchmarking API has been rewritten. bench is no longer a top-level import from vitest; it is a test-context fixture accessed from inside a regular test(). See the Benchmarking guide for the new API.
Removed, with replacements where applicable:
bench(name, fn)at module scope: destructurebenchfrom the test context instead.
// v4
import { bench } from 'vitest'
bench('sort', () => {
[3, 1, 2].sort()
})
// v5
import { test } from 'vitest'
test('sort', async ({ bench }) => {
await bench('sort', () => { [3, 1, 2].sort() }).run()
}) bench.skip,bench.only,bench.todoare removed. Use the regulartest.skip,test.only,test.todoon the surroundingtest()instead.benchmark.reporters/benchmark.outputFileare removed. Benchmark output is now part of the default reporter and thejsonreporter; configure those at the top level viatest.reportersinstead.benchmark.compareconfig and the--compareCLI flag are removed. PasswriteResultas a per-bench option to persist a result, and read it back withbench.from()insidebench.compare().benchmark.outputJsonconfig and the--outputJsonCLI flag are removed. Use--reporter=json --outputFile=<path>to capture benchmark results; the JSON reporter now includes abenchmarksfield on each test case.Vitestinstancemodeproperty is now always'test'. The previous'benchmark'value is no longer used; benchmarks run inside a dedicated project of the sameVitestinstance.
Vitest UI Requires an Authenticated URL
Vitest UI now requires token authentication for the HTML page and API access. The /__vitest__/ URL will show an error until the browser is authenticated. To authenticate, open the URL with a token printed by Vitest, as shown below. Once authenticated, the direct /__vitest__/ URL will work correctly.
vitest --ui
# UI started at http://localhost:51204/__vitest__/?token=...Fake Timers Now Mock Temporal
Vitest now mocks the Temporal API alongside Date when fake timers are enabled, following the @sinonjs/fake-timers v15.4 update. This only takes effect when Temporal is available on the global object — either natively (Node.js >= 26 by default, behind --harmony-temporal on older versions, and supporting browsers) or through a globally installed polyfill such as import 'temporal-polyfill/global'.
Previously Temporal.Now kept returning the real wall-clock time even when vi.useFakeTimers() was active. Now it follows the mocked clock:
vi.useFakeTimers({ now: 0 })
Temporal.Now.instant().epochMilliseconds // 0 (was the real time in v4)Temporal is part of the default set of faked APIs, so it is controlled by fakeTimers.toFake and fakeTimers.toNotFake. To keep Temporal native, add it to toNotFake:
vi.useFakeTimers({ toNotFake: ['Temporal'] })toThrow("") Matches Any Error Message
toThrow (and its alias toThrowError) treats a string argument as a substring of the error message. In Vitest 4 an empty string was special-cased to the /^$/ pattern, so it matched only an error whose message was empty. It now behaves like any other substring, and an empty string is contained in every message:
expect(() => { throw new Error('boom') }).not.toThrow('')
expect(() => { throw new Error('boom') }).toThrow('') To assert that a thrown error has an empty message, match the pattern explicitly:
expect(() => { throw new Error('boom') }).not.toThrow(/^$/)expect.poll Fails When It Times Out
expect.poll now rejects when its callback, or the polled assertion, does not settle within timeout. Previously a callback that resolved after the deadline, or an assertion that only passed on a late attempt, could still succeed. The callback now also receives an AbortSignal that aborts when the timeout elapses, so you can cancel in-flight work:
await expect.poll(async ({ signal }) => {
const response = await fetch('/api/status', { signal })
return response.status
}, { timeout: 1000 }).toBe(200)A poll that legitimately needs more time should raise its timeout. Otherwise it fails with expect.poll() function didn't resolve in time. (or expect.poll() assertion didn't resolve in time.).
Test Titles and Inspected Values Use pretty-format
Vitest now formats values with pretty-format instead of loupe when it inspects them, including the values interpolated into test.each and test.for titles. The rendering of some values changes, so snapshots or assertions that capture inspected output may need updating.
Two changes are specific to generated test titles:
- A string value interpolated through a
$placeholder is no longer wrapped in quotes:
test.for([{ id: 'a1' }])('case $id', ({ id }) => { /* ... */ })
// v4 title: case 'a1'
// v5 title: case a1- The length limit for interpolated values is now controlled by the new
taskTitleValueFormatTruncateoption (default40).
Removed test.sequential, describe.sequential, and sequential Options
Vitest 5.0 removes the deprecated test.sequential, describe.sequential, and sequential test options. Use concurrent: false when you need a test or suite to opt out of inherited or globally configured concurrency.
test.sequential('example', async () => { /* ... */ })
test('example', { concurrent: false }, async () => { /* ... */ }) describe.sequential('suite', () => { /* ... */ })
describe('suite', { concurrent: false }, () => { /* ... */ }) The same replacement applies to option objects:
test('example', { sequential: true }, async () => { /* ... */ })
test('example', { concurrent: false }, async () => { /* ... */ }) Locators in Commands are Serialized as Objects
Locators forwarded to browser commands are now serialized as a SerializedLocator object instead of a bare selector string. The object exposes two fields:
selector: the provider-specific selector string (the same value commands previously received).locator: a human-readable representation of the locator (e.g.getByRole('button')), used for error messages and tracing.
Update any custom commands that accept a locator to destructure selector from the new object:
import type { SerializedLocator } from '@vitest/browser'
import type { BrowserCommandContext } from 'vitest/node'
export async function customClick(
context: BrowserCommandContext,
selector: string,
{ selector }: SerializedLocator,
) {
await context.page.locator(selector).click()
}Locators are Strict by Default
Browser locators now match the text exactly by default, requiring a full, case-sensitive match. To keep the previous behaviour, you can set browser.locators.exact to false.
// With exact: true (default), this only matches the string "Hello, World" exactly.
// With exact: false, this matches "Hello, World!", "Say Hello, World", etc.
const locator = page.getByText('Hello, World', { exact: true })
await locator.click()toHaveTextContent Now Performs Strict Equality
The browser-mode toHaveTextContent matcher now validates that an element's text content is exactly equal to the expected string instead of performing a partial, case-sensitive match. Regular expressions are no longer accepted. The previous behaviour, including RegExp support, has moved to the new toMatchTextContent matcher.
// Partial or regex matches:
await expect.element(banner).toHaveTextContent('Error')
await expect.element(banner).toHaveTextContent(/error/i)
await expect.element(banner).toMatchTextContent('Error')
await expect.element(banner).toMatchTextContent(/error/i)
// Exact matches stay on `toHaveTextContent`:
await expect.element(banner).toHaveTextContent('Error!')render Is Async in vitest-browser-vue and vitest-browser-svelte
The companion component-testing packages vitest-browser-vue and vitest-browser-svelte now return a promise from render, so the call must be awaited before you query the rendered output:
import { render } from 'vitest-browser-vue'
import Component from './Component.vue'
test('renders', async () => {
const screen = render(Component)
const screen = await render(Component)
await expect.element(screen.getByRole('heading')).toBeVisible()
})Glob Coverage Thresholds No Longer Inherit perFile
coverage.thresholds.perFile previously applied to every threshold set, including files matched by glob-pattern thresholds. Glob patterns now control their own per-file checking and no longer inherit the top-level perFile — set perFile on each glob that needs it.
export default defineConfig({
test: {
coverage: {
thresholds: {
'perFile': true,
'src/utils/**': {
lines: 80,
perFile: true,
},
},
},
},
})Coverage include and exclude Match More Precisely
coverage.include and coverage.exclude were matched against absolute paths with picomatch's contains option, which matched many more files than intended. For example, a pattern could match a file because a parent directory in its absolute path happened to contain the same segment. Patterns are now matched against each file's path relative to the project root, without contains.
A pattern with no glob wildcard is treated as a directory and expanded to match everything inside it:
export default defineConfig({
test: {
coverage: {
include: ['src'], // matches src/**, not every path that contains "src"
},
},
})Review your include and exclude patterns after upgrading and confirm the reported file set is what you expect. Files that were previously matched only by the looser behavior may no longer be included.
Config Files Are Not Looked Up From Parent Directories
Vitest no longer searches parent directories for config files. If you previously relied on running vitest from a subdirectory while using a config file from a parent directory, pass the config explicitly and scope test discovery with --dir. For example,
$ cd subdir && vitest
$ cd subdir && vitest --config ../vitest.config.tsDOM Environment Global Assignments Now Update the Underlying Window
Assignments to properties on globalThis or window in jsdom and happy-dom environments are now propagated to the underlying DOM implementation. Mutable properties such as innerWidth can affect APIs implemented by the DOM environment, for example happy-dom's matchMedia.
populateGlobal Returns Descriptors in originals
The originals map returned by populateGlobal now holds property descriptors instead of plain values. This avoids invoking native lazy getters (such as Node's localStorage) while capturing the original, and restores them faithfully on teardown.
If you restore them manually in a custom environment, use Object.defineProperty instead of an assignment:
originals.forEach((value, key) => (global[key] = value))
originals.forEach((descriptor, key) => Object.defineProperty(global, key, descriptor)) Browser Orchestrator URL Requires a Session
Vitest no longer serves the browser orchestrator UI from a bare /__vitest_test__/ URL. Browser runner URLs are now session-bound and must include the sessionId generated by Vitest, for example /__vitest_test__/?sessionId=....
If you manually opened the browser preview by copying the Vite server URL or visiting /__vitest_test__/ directly, use the URL opened or printed by Vitest instead.
Generated Reports and Artifacts Use the .vitest Directory
Vitest now uses a single .vitest directory at the project root as the shared artifact root, so one .vitest entry in .gitignore is enough. Defaults that moved this major:
- Attachments (
attachmentsDir):.vitest-attachements/→.vitest/attachments/ - Blob reporter and
--merge-reports:.vitest-reports/blob-*.json→.vitest/blob/blob-*.json - HTML reporter (
html):html/index.html→.vitest/index.html, and its option changed fromoutputFile(a file) tooutputDir(a directory) - JSON reporter (
json): stdout →.vitest/json/output.json - JUnit reporter (
junit): stdout →.vitest/junit/output.xml
The json and junit reporters now write to a file by default instead of printing to stdout. If you previously relied on the report being printed to stdout (for example vitest --reporter=json > out.json or vitest --reporter=json | jq), either read the generated artifact file instead (for example jq . .vitest/json/output.json), or opt back into stdout with the reporter's stdout option (reporters: [['json', { stdout: true }]]). An explicit outputFile is still respected and unchanged.
toMatchScreenshot Now Uses a Dedicated Screenshot Directory Config
Previously, reference screenshots for toMatchScreenshot did not correctly respect browser.screenshotDirectory. As a result, screenshots were saved in an unintended location when a custom directory was configured.
This has now been fixed by introducing a dedicated option: browser.expect.toMatchScreenshot.screenshotDirectory. Its default value is __screenshots__.
If you did not set
browser.screenshotDirectory, no changes are required.If you did set
browser.screenshotDirectory, you must now explicitly configure the new option:tsexport default defineConfig({ test: { browser: { screenshotDirectory: 'my-screenshots', expect: { toMatchScreenshot: { screenshotDirectory: 'my-screenshots', }, }, }, }, })Then either move existing reference screenshots to the new location or regenerate them.
Worker and Concurrency Ids Are 1-based
Worker and pool identifiers now start at 1 instead of 0. This changes the values of the VITEST_POOL_ID and VITEST_WORKER_ID environment variables, which now range from 1 to the worker count. Update any logic that derives a value from these ids, such as a per-worker database name or an array index.
For custom reporters, the TestModule diagnostics now expose both ids: the existing workerId (now 1-based) and a new concurrencyId.
import type { Reporter, TestModule } from 'vitest/node'
class MyReporter implements Reporter {
onTestModuleEnd(testModule: TestModule) {
const { workerId, concurrencyId } = testModule.diagnostic()
}
}Node.js and browser tests run in separate pools and do not share these ids, so the same value can appear in both.
Package Migration
The following packages are deprecated as of this release. They will no longer receive feature updates, but security fixes will continue to be backported:
The @vitest/browser-webdriverio provider has been moved to the vitest-community organization. Going forward, WebdriverIO support is community-maintained and addressed on a per-issue basis. If you use it, update your dependency to the new package and report any issues in the new repository.
Removed Deprecated Entrypoints
Several entry points were marked as deprecated in Vitest 4.1. This release removes them entirely.
vitest/coverage: usevitest/nodeinsteadvitest/reporters: usevitest/nodeinsteadvitest/environments: usevitest/runtimeinsteadvitest/snapshot: usevitest/runtimeinsteadvitest/runners: useTestRunnerfromvitestinsteadvitest/suite: use static methods onTestRunnerfrom vitest instead (for example,TestRunner.getCurrentTest())vitest/mockeris removed completely, use@vitest/mockerpackage directly (this was published by accident at one point and never removed)vitest/internal/module-runneris removed
Migrating from Jest
Vitest has been designed with a Jest compatible API, in order to make the migration from Jest as simple as possible. Despite those efforts, you may still run into the following differences:
Globals as a Default
Jest has their globals API enabled by default. Vitest does not. You can either enable globals via the globals configuration setting or update your code to use imports from the vitest module instead.
If you decide to keep globals disabled, be aware that common libraries like testing-library will not run auto DOM cleanup.
mock.mockReset
Jest's mockReset replaces the mock implementation with an empty function that returns undefined.
Vitest's mockReset resets the mock implementation to its original. That is, resetting a mock created by vi.fn(impl) will reset the mock implementation to impl.
mock.mock is Persistent
Jest will recreate the mock state when .mockClear is called, meaning you always need to access it as a getter. Vitest, on the other hand, holds a persistent reference to the state, meaning you can reuse it:
const mock = vi.fn()
const state = mock.mock
mock.mockClear()
expect(state).toBe(mock.mock) // fails in JestModule Mocks
When mocking a module in Jest, the factory argument's return value is the default export. In Vitest, the factory argument has to return an object with each export explicitly defined. For example, the following jest.mock would have to be updated as follows:
jest.mock('./some-path', () => 'hello')
vi.mock('./some-path', () => ({
default: 'hello',
})) For more details please refer to the vi.mock api section.
Auto-Mocking Behaviour
Unlike Jest, mocked modules in <root>/__mocks__ are not loaded unless vi.mock() is called. If you need them to be mocked in every test, like in Jest, you can mock them inside setupFiles.
Importing the Original of a Mocked Package
If you are only partially mocking a package, you might have previously used Jest's function requireActual. In Vitest, you should replace these calls with vi.importActual.
const { cloneDeep } = jest.requireActual('lodash/cloneDeep')
const { cloneDeep } = await vi.importActual('lodash/cloneDeep') Extends mocking to external libraries
Where Jest does it by default, when mocking a module and wanting this mocking to be extended to other external libraries that use the same module, you should explicitly tell which 3rd-party library you want to be mocked, so the external library would be part of your source code, by using server.deps.inline.
server.deps.inline: ["lib-name"]expect.getState().currentTestName
Vitest's test names are joined with a > symbol to make it easier to distinguish tests from suites, while Jest uses an empty space ().
- `${describeTitle} ${testTitle}`
+ `${describeTitle} > ${testTitle}`Envs
Just like Jest, Vitest sets NODE_ENV to test, if it wasn't set before. Vitest also has a counterpart for JEST_WORKER_ID called VITEST_POOL_ID (always less than or equal to maxWorkers), so if you rely on it, don't forget to rename it. Vitest also exposes VITEST_WORKER_ID which is a unique ID of a running worker - this number is not affected by maxWorkers, and will increase with each created worker.
Replace property
If you want to modify the object, you will use replaceProperty API in Jest, you can use vi.stubEnv or vi.spyOn to do the same also in Vitest.
Done Callback
Vitest does not support the callback style of declaring tests. You can rewrite them to use async/await functions, or use Promise to mimic the callback style.
it('should work', (done) => {
it('should work', () => new Promise(done => {
// ...
done()
})
})) Hooks
beforeAll/beforeEach hooks may return teardown function in Vitest. Because of that you may need to rewrite your hooks declarations, if they return something other than undefined or null:
beforeEach(() => setActivePinia(createTestingPinia()))
beforeEach(() => { setActivePinia(createTestingPinia()) }) In Jest hooks are called sequentially (one after another). By default, Vitest runs hooks in a stack. To use Jest's behavior, update sequence.hooks option:
export default defineConfig({
test: {
sequence: {
hooks: 'list',
}
}
})Types
Vitest doesn't have an equivalent to jest namespace, so you will need to import types directly from vitest:
let fn: jest.Mock<(name: string) => number>
import type { Mock } from 'vitest'
let fn: Mock<(name: string) => number> Timers
Vitest doesn't support Jest's legacy timers.
Timeout
If you used jest.setTimeout, you would need to migrate to vi.setConfig:
jest.setTimeout(5_000)
vi.setConfig({ testTimeout: 5_000 }) Vue Snapshots
This is not a Jest-specific feature, but if you previously were using Jest with vue-cli preset, you will need to install jest-serializer-vue package, and specify it in snapshotSerializers:
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
snapshotSerializers: ['jest-serializer-vue']
}
})Otherwise your snapshots will have a lot of escaped " characters.
Custom Snapshot Matchers experimental 4.1.3+
Jest imports snapshot composables from jest-snapshot. In Vitest, use Snapshots from vitest instead:
const { toMatchSnapshot } = require('jest-snapshot')
import { Snapshots } from 'vitest'
const { toMatchSnapshot } = Snapshots
expect.extend({
toMatchTrimmedSnapshot(received: string, length: number) {
return toMatchSnapshot.call(this, received.slice(0, length))
},
})For inline snapshots, the same applies:
const { toMatchInlineSnapshot } = require('jest-snapshot')
import { Snapshots } from 'vitest'
const { toMatchInlineSnapshot } = Snapshots
expect.extend({
toMatchTrimmedInlineSnapshot(received: string, inlineSnapshot?: string) {
return toMatchInlineSnapshot.call(this, received.slice(0, 10), inlineSnapshot)
},
})See Custom Snapshot Matchers for the full guide.
Migrating from Mocha + Chai + Sinon
Vitest provides excellent support for migrating from Mocha+Chai+Sinon test suites. While Vitest uses a Jest-compatible API by default, it also provides Chai-style assertions for spy/mock testing, making migration easier.
Test Structure
Mocha and Vitest have similar test structures, but with some differences:
// Mocha
describe('suite', () => {
before(() => { /* setup */ })
after(() => { /* teardown */ })
beforeEach(() => { /* setup */ })
afterEach(() => { /* teardown */ })
it('test', () => {
// test code
})
})
// Vitest - same structure works!
import { afterAll, afterEach, beforeAll, beforeEach, describe, it } from 'vitest'
describe('suite', () => {
beforeAll(() => { /* setup */ })
afterAll(() => { /* teardown */ })
beforeEach(() => { /* setup */ })
afterEach(() => { /* teardown */ })
it('test', () => {
// test code
})
})Assertions
Vitest includes Chai assertions by default, so Chai assertions work without changes:
// Both Mocha+Chai and Vitest
import { expect } from 'vitest' // or 'chai' in Mocha
expect(value).to.equal(42)
expect(value).to.be.true
expect(array).to.have.lengthOf(3)
expect(obj).to.have.property('key')Spy/Mock Assertions
Vitest provides Chai-style assertions for spies and mocks, allowing you to migrate from Sinon without rewriting assertions:
// Before (Mocha + Chai + Sinon)
const sinon = require('sinon')
const chai = require('chai')
const sinonChai = require('sinon-chai')
chai.use(sinonChai)
const spy = sinon.spy(obj, 'method')
obj.method('arg1', 'arg2')
expect(spy).to.have.been.called
expect(spy).to.have.been.calledOnce
expect(spy).to.have.been.calledWith('arg1', 'arg2')
// After (Vitest) - same assertion syntax!
import { expect, vi } from 'vitest'
const spy = vi.spyOn(obj, 'method')
obj.method('arg1', 'arg2')
expect(spy).to.have.been.called
expect(spy).to.have.been.calledOnce
expect(spy).to.have.been.calledWith('arg1', 'arg2')Complete Chai-Style Assertion Support
Vitest supports all common sinon-chai assertions:
| Sinon-Chai | Vitest | Description |
|---|---|---|
spy.called | called | Spy was called at least once |
spy.calledOnce | calledOnce | Spy was called exactly once |
spy.calledTwice | calledTwice | Spy was called exactly twice |
spy.calledThrice | calledThrice | Spy was called exactly three times |
spy.callCount(n) | callCount(n) | Spy was called n times |
spy.calledWith(...) | calledWith(...) | Spy was called with specific args |
spy.calledOnceWith(...) | calledOnceWith(...) | Spy was called once with specific args |
spy.returned(value) | returned | Spy returned specific value |
See the Chai-Style Spy Assertions documentation for the complete list.
Creating Spies and Mocks
Replace Sinon's spy/stub/mock creation with Vitest's vi utilities:
// Sinon
const sinon = require('sinon')
const spy = sinon.spy()
const stub = sinon.stub(obj, 'method')
const mock = sinon.mock(obj)
// Vitest
import { vi } from 'vitest'
const spy = vi.fn()
const stub = vi.spyOn(obj, 'method')
// Vitest doesn't have "mocks" - use spies insteadStubbing Return Values
// Sinon
stub.returns(42)
stub.onFirstCall().returns(1)
stub.onSecondCall().returns(2)
// Vitest
stub.mockReturnValue(42)
stub.mockReturnValueOnce(1)
stub.mockReturnValueOnce(2)Stubbing Implementations
// Sinon
stub.callsFake(arg => arg * 2)
// Vitest
stub.mockImplementation(arg => arg * 2)Restoring Spies
// Sinon
spy.restore()
sinon.restore() // restore all
// Vitest
spy.mockRestore()
vi.restoreAllMocks() // restore allTimers
Both Sinon and Vitest use @sinonjs/fake-timers internally:
// Sinon
const clock = sinon.useFakeTimers()
clock.tick(1000)
clock.restore()
// Vitest
import { vi } from 'vitest'
vi.useFakeTimers()
vi.advanceTimersByTime(1000)
vi.useRealTimers()Key Differences
- Globals: Mocha provides globals by default. In Vitest, either import from
vitestor enableglobalsconfig - Assertion style: You can use both Chai-style (
expect(spy).to.have.been.called) and Jest-style (expect(spy).toHaveBeenCalled()) - Parallel execution: Vitest runs tests in parallel by default, Mocha runs sequentially
For more information, see: