Vitest in 2026: The Testing Framework That Makes You Actually Want to Write Tests
Let's be honest: most developers don't enjoy writing tests. They're slow to set up, painful to maintain, and Jest's configuration is... let's call it 'interesting'. Vitest changes the game entirely.
What is Vitest?
Vitest is a blazing-fast unit testing framework powered by Vite. It uses the same configuration, transforms, and resolvers as your Vite app โ meaning zero additional setup for projects already using Vite (React, Vue, Svelte, SvelteKit, Nuxt, Astro...).
Why developers love it:
- โก 10-20x faster than Jest on large codebases
- ๐ง Zero config (if you use Vite)
- ๐ญ Jest-compatible API (migrate in minutes)
- ๐ Native TypeScript support (no ts-jest nonsense)
- ๐ฅ HMR for tests (tests re-run only when affected files change)
Getting Started
npm install -D vitest
If you're using Vite, add to your vite.config.ts:
import { defineConfig } from 'vite'
import { defineConfig as defineTestConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // Use describe/it/expect without imports
environment: 'node', // 'jsdom' for browser-like tests
coverage: {
provider: 'v8', // or 'istanbul'
reporter: ['text', 'html', 'json'],
},
},
})
For standalone projects:
npm install -D vitest
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui"
}
}
Your First Tests
// src/utils/math.ts
export const add = (a: number, b: number) => a + b
export const divide = (a: number, b: number) => {
if (b === 0) throw new Error('Division by zero')
return a / b
}
// src/utils/math.test.ts
import { describe, it, expect } from 'vitest'
import { add, divide } from './math'
describe('Math utilities', () => {
describe('add', () => {
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('should handle negative numbers', () => {
expect(add(-1, 1)).toBe(0)
})
})
describe('divide', () => {
it('should divide correctly', () => {
expect(divide(10, 2)).toBe(5)
})
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero')
})
})
})
Mocking โ Vitest's Superpower
import { vi, describe, it, expect, beforeEach } from 'vitest'
// Mock an entire module
vi.mock('./database', () => ({
db: {
findUser: vi.fn(),
saveUser: vi.fn(),
}
}))
// Mock a specific function
const fetchMock = vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ id: '1', name: 'Alice' })
})
vi.stubGlobal('fetch', fetchMock)
// Spy on existing functions
import { db } from './database'
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks() // Reset mocks between tests
})
it('should fetch and save a user', async () => {
vi.mocked(db.findUser).mockResolvedValue(null)
vi.mocked(db.saveUser).mockResolvedValue({ id: '1', name: 'Alice' })
const result = await createUser({ name: 'Alice', email: 'alice@example.com' })
expect(db.saveUser).toHaveBeenCalledOnce()
expect(db.saveUser).toHaveBeenCalledWith({
name: 'Alice',
email: 'alice@example.com'
})
expect(result.name).toBe('Alice')
})
})
Testing React Components with Vitest
npm install -D @testing-library/react @testing-library/jest-dom jsdom
// vite.config.ts โ add jsdom environment
export default defineConfig({
test: {
environment: 'jsdom',
setupFiles: ['./src/test-setup.ts'],
},
})
// src/test-setup.ts
import '@testing-library/jest-dom'
// src/components/Counter.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { Counter } from './Counter'
describe('Counter', () => {
it('renders with initial count', () => {
render(<Counter initialCount={5} />)
expect(screen.getByText('Count: 5')).toBeInTheDocument()
})
it('increments on button click', () => {
render(<Counter initialCount={0} />)
fireEvent.click(screen.getByRole('button', { name: '+' }))
expect(screen.getByText('Count: 1')).toBeInTheDocument()
})
})
Vitest UI โ The Game Changer
npm install -D @vitest/ui
vitest --ui
This opens a browser-based UI showing:
- All your test files and suites
- Pass/fail status with details
- Coverage reports
- Real-time updates as you code
It's genuinely beautiful and makes debugging tests 10x easier.
Snapshot Testing
import { it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { Button } from './Button'
it('renders correctly', () => {
const { container } = render(<Button variant="primary">Click me</Button>)
expect(container).toMatchSnapshot()
})
// Inline snapshots (great for small outputs)
it('formats currency', () => {
expect(formatCurrency(1234.56, 'EUR')).toMatchInlineSnapshot('"โฌ1,234.56"')
})
Benchmarking (Bonus Feature)
import { bench, describe } from 'vitest'
describe('Array operations', () => {
bench('filter + map', () => {
[1,2,3,4,5].filter(n => n > 2).map(n => n * 2)
})
bench('reduce', () => {
[1,2,3,4,5].reduce((acc, n) => n > 2 ? [...acc, n * 2] : acc, [] as number[])
})
})
Migrating from Jest
Good news: Vitest is mostly Jest-compatible. The migration is usually:
npm uninstall jest @types/jest babel-jest ts-jestnpm install -D vitest- Change imports:
from 'jest'โfrom 'vitest' - Replace
jest.fn()โvi.fn(),jest.mock()โvi.mock() - Update
package.jsonscripts
For most projects, that's it. Total time: 30-60 minutes.
Performance Comparison
| Metric | Jest | Vitest |
|---|---|---|
| Cold start | ~2-4s | ~200ms |
| 500 tests | ~45s | ~8s |
| TypeScript | via ts-jest | native |
| ESM support | painful | native |
| HMR | โ | โ |
Conclusion
Vitest has become the default testing framework for modern JavaScript/TypeScript projects in 2026. If you're starting a new Vite project, it's a no-brainer. If you're on Jest and feeling the pain, the migration is easier than you think.
The bottom line: better DX = more tests written = better software.
Building a freelance dev business? My Freelancer OS Notion Template helps you manage clients, projects, and finances in one place โ โฌ19.
Happy testing! ๐งช
Top comments (0)