Vitest in 2026: The New Standard for Modern JavaScript Testing
Hello HaWkers, if you are still using Jest in new projects, you are probably wasting time and performance. Vitest has established itself as the standard testing framework for the modern JavaScript ecosystem in 2026.
Let's understand why the change happened and how you can benefit.
Why Vitest Won
The Context of Change
// Timeline of the shift from Jest to Vitest
const testingTimeline = {
2020: 'Jest is the undisputed standard',
2022: 'Vitest 0.x emerges with Vite',
2023: 'Vitest 1.0 - production ready',
2024: {
vitest: 'Massive adoption in new projects',
jest: 'Still dominant in legacy'
},
2025: {
vitest4: 'Vitest 4.0 stable',
angular: 'Angular 21 adopts Vitest as default',
breaking: 'Turning point'
},
2026: {
status: 'Vitest is the new standard',
jest: 'Maintained for legacy',
newProjects: '80%+ use Vitest'
}
};Vitest Advantages
// Why Vitest surpassed Jest
const vitestAdvantages = {
// 1. Performance
performance: {
speedup: '2-10x faster than Jest',
why: 'Reuses Vite pipeline',
watch: 'Instant watch mode',
benchmark: `
Medium project (500 tests):
Jest: 45 seconds
Vitest: 8 seconds
`
},
// 2. Zero configuration with Vite
zeroConfig: {
if: 'You use Vite',
then: 'Vitest works without config',
shared: 'Same plugin config, aliases, etc',
dx: 'No need to maintain two configs'
},
// 3. Native ESM
esm: {
jest: 'ESM is an add-on, frequently breaks',
vitest: 'ESM is native, works perfectly',
impact: 'Can test ESM-only code without hacks'
},
// 4. Native TypeScript
typescript: {
jest: 'Requires ts-jest or babel',
vitest: 'TypeScript works out of the box',
types: 'Types included, no @types/vitest needed'
},
// 5. Jest-compatible API
compatibility: {
api: 'describe, it, expect - same API',
migration: 'Most tests work without changes',
mocks: 'vi.mock() similar to jest.mock()'
},
// 6. Modern features
modernFeatures: {
browserMode: 'Test in real browser (Playwright)',
snapshotInline: 'Inline snapshots in code',
coverage: 'Native v8 coverage',
ui: 'Built-in interactive UI'
}
};
Getting Started with Vitest
Basic Setup
// Installation and configuration
// 1. Install
// npm install -D vitest
// 2. Add script to package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
// 3. Configuration (optional - works without config with Vite)
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
// Globals (describe, it, expect) without import
globals: true,
// Test environment
environment: 'jsdom', // or 'node', 'happy-dom'
// Setup files
setupFiles: ['./tests/setup.ts'],
// Coverage
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
exclude: ['node_modules/', 'tests/']
},
// Include/exclude files
include: ['**/*.{test,spec}.{ts,tsx}'],
},
});Writing Tests
// Basic syntax - very similar to Jest
// math.test.ts
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { sum, multiply } from './math';
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
it('should multiply two numbers', () => {
expect(multiply(3, 4)).toBe(12);
});
});
// With globals: true in config, you can omit imports
describe('Math functions', () => {
it('should sum two numbers', () => {
expect(sum(1, 2)).toBe(3);
});
});Mocking
// Mocking in Vitest
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from './api';
import { UserService } from './UserService';
// Mock entire module
vi.mock('./api', () => ({
fetchUser: vi.fn()
}));
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks();
});
it('should fetch user by id', async () => {
// Configure mock
vi.mocked(fetchUser).mockResolvedValue({
id: 1,
name: 'John'
});
const service = new UserService();
const user = await service.getUser(1);
expect(fetchUser).toHaveBeenCalledWith(1);
expect(user.name).toBe('John');
});
});
// Mock specific function
const mockFn = vi.fn();
mockFn.mockReturnValue(42);
mockFn.mockResolvedValue(asyncValue);
mockFn.mockImplementation((x) => x * 2);
// Spy on existing method
const spy = vi.spyOn(object, 'method');
spy.mockReturnValue('mocked');
// Mock timers
vi.useFakeTimers();
vi.advanceTimersByTime(1000);
vi.runAllTimers();
vi.useRealTimers();
Testing Components
// Testing React components with Vitest + Testing Library
// Button.test.tsx
import { describe, it, expect } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('should render with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('should call onClick when clicked', async () => {
const handleClick = vi.fn();
render(<Button onClick={handleClick}>Click</Button>);
await fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledOnce();
});
it('should be disabled when loading', () => {
render(<Button loading>Submit</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
});
// Setup for Testing Library (tests/setup.ts)
import '@testing-library/jest-dom/vitest';Testing Vue
// Testing Vue components with Vitest
// Counter.test.ts
import { describe, it, expect } from 'vitest';
import { mount } from '@vue/test-utils';
import Counter from './Counter.vue';
describe('Counter', () => {
it('should render initial count', () => {
const wrapper = mount(Counter, {
props: { initialCount: 5 }
});
expect(wrapper.text()).toContain('5');
});
it('should increment when button clicked', async () => {
const wrapper = mount(Counter);
await wrapper.find('button').trigger('click');
expect(wrapper.text()).toContain('1');
});
});Advanced Features
Browser Mode
// Test in real browser with Playwright
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
browser: {
enabled: true,
name: 'chromium', // or 'firefox', 'webkit'
provider: 'playwright',
},
},
});
// Tests run in real browser!
// Useful for:
// - Canvas, WebGL
// - Specific Web APIs
// - Visual regression
// visual.test.ts
import { describe, it, expect } from 'vitest';
import { page } from '@vitest/browser/context';
describe('Visual tests', () => {
it('should render chart correctly', async () => {
await page.goto('/chart');
// Screenshot for visual regression
await expect(page.locator('.chart')).toMatchScreenshot();
});
});
Inline Snapshots
// Inline snapshots - more convenient than files
import { describe, it, expect } from 'vitest';
import { formatUser } from './formatUser';
describe('formatUser', () => {
it('should format user object', () => {
const user = { name: 'John', age: 30 };
// Inline snapshot - updates in the file itself!
expect(formatUser(user)).toMatchInlineSnapshot(`
{
"displayName": "John (30 years old)",
"initials": "J"
}
`);
});
});
// When you run vitest -u, the snapshot is updated
// directly in the test fileType Testing
// Test TypeScript types
// types.test-d.ts (note the -d in the name)
import { describe, it, expectTypeOf } from 'vitest';
import { createStore } from './store';
describe('Store types', () => {
it('should infer state type correctly', () => {
const store = createStore({ count: 0 });
// Test if type is correct
expectTypeOf(store.state.count).toBeNumber();
expectTypeOf(store.state.count).not.toBeString();
});
it('should accept correct action types', () => {
type Action = { type: 'increment' } | { type: 'decrement' };
expectTypeOf<Action>().toMatchTypeOf<{ type: string }>();
});
});
// vitest.config.ts
export default defineConfig({
test: {
typecheck: {
enabled: true,
},
},
});UI Mode
// Visual interface for tests
// Run: npx vitest --ui
// UI features:
// - View all tests
// - Filter by status (passed, failed, skipped)
// - See test code
// - See console output
// - Re-run specific tests
// - Module graph visualization
// Excellent for:
// - Debugging failing tests
// - Understanding coverage
// - Onboarding new devs
Migrating from Jest to Vitest
Migration Guide
// Steps to migrate
const migrationGuide = {
// 1. Install Vitest
step1: `
npm remove jest @types/jest ts-jest babel-jest
npm install -D vitest
`,
// 2. Create config
step2: `
// vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true, // For Jest compatibility
environment: 'jsdom',
},
});
`,
// 3. Update scripts
step3: `
// package.json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
`,
// 4. Search and replace
step4: {
from: "jest.fn()",
to: "vi.fn()",
from2: "jest.mock(",
to2: "vi.mock(",
from3: "jest.spyOn(",
to3: "vi.spyOn(",
from4: "@jest/globals",
to4: "vitest"
},
// 5. Update imports (if not using globals)
step5: `
// Before
import { jest } from '@jest/globals';
// After
import { vi } from 'vitest';
`
};API Differences
// Differences between Jest and Vitest
const apiDifferences = {
// Mocking
mocking: {
jest: 'jest.fn(), jest.mock(), jest.spyOn()',
vitest: 'vi.fn(), vi.mock(), vi.spyOn()',
note: 'Only different prefix'
},
// Timers
timers: {
jest: 'jest.useFakeTimers()',
vitest: 'vi.useFakeTimers()',
extra: 'Vitest has vi.advanceTimersByTimeAsync()'
},
// Module mocking
moduleMock: {
jest: `
jest.mock('./module', () => ({
foo: jest.fn()
}));
`,
vitest: `
vi.mock('./module', () => ({
foo: vi.fn()
}));
`,
vitestAlternative: `
// Vitest also supports:
vi.mock('./module', async (importOriginal) => {
const actual = await importOriginal();
return {
...actual,
foo: vi.fn()
};
});
`
},
// Snapshot
snapshot: {
jest: 'expect(x).toMatchSnapshot()',
vitest: 'expect(x).toMatchSnapshot()',
extra: 'Vitest has toMatchInlineSnapshot()'
},
// Globals
globals: {
jest: 'Globals by default',
vitest: 'Needs globals: true or explicit imports',
recommendation: 'Use explicit imports for type safety'
}
};Automatic Codemod
// Use codemod for automated migration
// Install
// npx @vitest/codemod jest
// The codemod does:
// - Renames jest.* to vi.*
// - Updates imports
// - Adjusts configuration
// Afterward, manually review:
// - Complex mocks
// - Setup files
// - Custom matchers
Best Practices with Vitest
Test Organization
// Recommended structure
const testStructure = {
// Option 1: Co-located (recommended)
colocated: `
src/
components/
Button/
Button.tsx
Button.test.tsx ← Next to the code
Button.stories.tsx
utils/
format.ts
format.test.ts ← Next to the code
`,
// Option 2: Separate folder
separate: `
src/
components/
Button.tsx
tests/
components/
Button.test.tsx ← Separate folder
`,
recommendation: `
Co-located is better:
- Easy to find tests
- Refactor together
- Encourages writing tests
`
};Useful Patterns
// Patterns that work well
// 1. Factory functions for fixtures
function createUser(overrides = {}) {
return {
id: 1,
name: 'John',
email: 'john@example.com',
...overrides
};
}
it('should update user name', () => {
const user = createUser({ name: 'Jane' });
// ...
});
// 2. Custom matchers
expect.extend({
toBeValidEmail(received) {
const pass = /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(received);
return {
pass,
message: () => `expected ${received} to be valid email`
};
}
});
// 3. Testing utilities
function renderWithProviders(ui, options = {}) {
return render(ui, {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
<ThemeProvider>
{children}
</ThemeProvider>
</QueryClientProvider>
),
...options
});
}
// 4. Organized describe blocks
describe('UserService', () => {
describe('getUser', () => {
it('should return user when found', () => {});
it('should throw when not found', () => {});
});
describe('createUser', () => {
it('should create user with valid data', () => {});
it('should reject invalid email', () => {});
});
});
Conclusion
Vitest has become the standard for JavaScript testing for good reasons: it is faster, more modern, and has better DX than Jest. Migration is simple and the benefits are immediate.
When to use Vitest:
- New projects (always)
- Projects with Vite (easy migration)
- Projects that need ESM
- When test performance matters
When to keep Jest:
- Legacy projects working fine
- Migration cost does not pay off
- Dependencies that require Jest
Recommended actions:
- Use Vitest in new projects
- Plan migration for active projects
- Explore browser mode for visual tests
- Use UI mode for debugging
To learn more about the modern JavaScript ecosystem, read: VoidZero 2026.

