Vitest is a unit test framework built by the Vite team. It reuses your existing Vite config — TypeScript, JSX, path aliases, and environment variables all work in tests without extra setup. It implements nearly the entire Jest API, so migrating existing test suites is usually a matter of swapping the import.
Why Vitest Instead of Jest
A typical Jest setup for TypeScript + Vite needs:
-
babel-jestorts-jestto handle TypeScript - A Babel config or
jest.config.tswithtransform - Separate handling for ESM packages
- Path alias replication (your
vite.config.tsaliases don't work in Jest)
With Vitest: none of that. Your existing vite.config.ts is already the Vitest config.
Installation
npm install -D vitest
Add to package.json:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
Configuration
// vite.config.ts
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tsconfigPaths from 'vite-tsconfig-paths'
export default defineConfig({
plugins: [react(), tsconfigPaths()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
thresholds: { lines: 80, functions: 80 }
}
}
})
Basic Test Structure
import { describe, it, expect } from 'vitest'
import { slugify, truncate } from './string-utils'
describe('slugify', () => {
it('converts spaces to hyphens', () => {
expect(slugify('Hello World')).toBe('hello-world')
})
it('removes special characters', () => {
expect(slugify('TypeScript: The Good Parts!')).toBe('typescript-the-good-parts')
})
})
describe('truncate', () => {
it('truncates and appends ellipsis', () => {
expect(truncate('hello world', 8)).toBe('hello...')
})
})
Mocking with vi
The vi object replaces jest:
import { vi, describe, it, expect, beforeEach } from 'vitest'
// Mock a function
const mockFn = vi.fn().mockReturnValue('result')
expect(mockFn).toHaveBeenCalledOnce()
// Mock entire module
vi.mock('./database', () => ({
db: {
user: { findUnique: vi.fn(), create: vi.fn() }
}
}))
// Partial mock — keep real implementation, mock one export
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal<typeof import('./utils')>()
return { ...actual, sendEmail: vi.fn() }
})
// Fake timers
vi.useFakeTimers()
vi.setSystemTime(new Date('2026-01-15'))
vi.advanceTimersByTime(24 * 60 * 60 * 1000)
vi.useRealTimers()
React Component Testing
npm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom
// src/test/setup.ts
import '@testing-library/jest-dom'
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vitest'
import SearchBar from './SearchBar'
describe('SearchBar', () => {
it('calls onSearch with input value on submit', async () => {
const user = userEvent.setup()
const onSearch = vi.fn()
render(<SearchBar onSearch={onSearch} />)
await user.type(screen.getByRole('textbox'), 'vitest guide')
await user.click(screen.getByRole('button', { name: /search/i }))
expect(onSearch).toHaveBeenCalledWith('vitest guide')
})
it('shows no results message when empty', async () => {
const user = userEvent.setup()
render(<SearchBar onSearch={vi.fn().mockResolvedValue([])} />)
await user.type(screen.getByRole('textbox'), 'xyzabc')
await user.click(screen.getByRole('button', { name: /search/i }))
await waitFor(() => {
expect(screen.getByText(/no results/i)).toBeInTheDocument()
})
})
})
Vitest UI
npm install -D @vitest/ui
npx vitest --ui
Opens a browser-based dashboard at http://localhost:51204 with live test results, filterable test tree, and console output per test.
Vitest vs Jest
| Vitest | Jest | |
|---|---|---|
| Speed | Fast (esbuild) | Slower (Babel) |
| TypeScript | Native with Vite | Needs config |
| ESM | Native | Needs workarounds |
| Config | Extends vite.config.ts | Separate config |
| API | Jest-compatible | Original |
| Mocking | vi.* |
jest.* |
| Browser testing | @vitest/browser | jsdom only |
Migration from Jest is mostly mechanical:
- Replace
jest.*withvi.* - Remove Jest config and Babel transform setup
- Add
globals: truein Vitest config - Most test code runs without changes
Full article at stacknotice.com/blog/vitest-complete-guide-2026
Top comments (0)