Vitest 2 ships a stable Browser Mode, in-source testing, smarter watch, and project-aware workspaces. Most Jest APIs map 1:1 (jest → vi, identical matchers); biggest differences are ESM-native module resolution and config living under Vite’s test: key. The vitest CLI watches by default — use vitest run in CI.
Install · configSetup
bash
# Install (works on top of Vite or standalone)
npm i -D vitest @vitest/coverage-v8
# For DOM tests
npm i -D jsdom happy-dom @testing-library/dom @testing-library/react @testing-library/jest-dom
# For UI runner
npm i -D @vitest/ui
# vitest.config.ts (or extend vite.config.ts)
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node", // or "jsdom" | "happy-dom" | "edge-runtime"
globals: false, // true = auto-inject expect, vi, test, describe
setupFiles: ["./test/setup.ts"],
coverage: { provider: "v8", reporter: ["text", "lcov", "html"] },
},
});
# package.json
{
"scripts": {
"test": "vitest", # watch by default
"test:run": "vitest run", # one-shot (CI)
"test:ui": "vitest --ui",
"test:cov": "vitest run --coverage"
}
}
# Run
npx vitest # watch
npx vitest run path/to/file.test.ts # one file, no watch
npx vitest -t "login" # name filter
npx vitest --reporter=verbose
npx vitest --bail=1 --no-color
Where things liveCommon imports
Set test.globals: true in vitest.config.ts to auto-inject the API; otherwise import explicitly. Explicit imports give you cleaner type narrowing.
Mostly drop-in for Jest tests.
Run codemod-jest-to-vitest or swap jest → vi by hand. Inline snapshots and module factories carry over.
Use vitest run in CI, plain vitest locally.
Forgetting run in CI hangs the pipeline in watch mode — the most-common Vitest paper cut.
Prefer happy-dom over jsdom when speed matters.
Smaller, faster startup; jsdom is more compatible for legacy DOM corners. Workspace projects let you pick per package.
Common trapsWatch out for
Forgetting await on advanceTimersByTimeAsync.advanceTimersByTime doesn’t flush microtasks — an async setTimeout callback never runs.
Module hoisting bites the same way it does in Jest.
The vi.mock() factory cannot reference top-level consts — reach for vi.hoisted().
Browser mode needs a provider (playwright / webdriverio).
Enabling test.browser.enabled without installing a provider yields a cryptic launcher error.
Vitest is a Vite-native test runner for JavaScript and TypeScript. It uses the same config as your Vite project, supports ESM natively without transformation hacks, provides Jest-compatible matchers, and offers browser mode for component testing. The vitest CLI watches tests by default.
Is Vitest compatible with Jest?
Mostly yes. Vitest uses the same describe/it/expect/beforeEach/afterEach API as Jest. Most Jest tests migrate with a find-and-replace of the jest global to vi. The main differences are ESM-native module resolution, vi.mock() hoisting behavior, and configuration under Vite instead of jest.config.js.
How do mocks work in Vitest?
vi.fn() creates a spy function that records calls. vi.spyOn(obj, 'method') wraps an existing method. vi.mock('./module') replaces an entire module with auto-mocked stubs. vi.stubGlobal() sets global values. All mocks reset between tests when you call vi.clearAllMocks() or set clearMocks: true in config.
How does coverage work in Vitest?
Run vitest run --coverage to generate a coverage report. Vitest uses V8 or Istanbul — install @vitest/coverage-v8 or @vitest/coverage-istanbul. Set thresholds in vitest.config.ts under coverage.thresholds. Coverage reports can output as text, lcov, html, or json for CI pipelines.
What is the difference between Vitest and Jest?
Vitest is faster for Vite-based projects because it re-uses the Vite pipeline instead of running a separate transform. It supports ESM natively and runs tests in parallel across workers. Jest has a larger ecosystem of plugins and runners, broader community docs, and better support for non-Vite projects.