Vitest Setup
Vitest Setup
Installation
npm install --save-dev vitest
vitest.config.ts
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['src/**/*.test.ts', 'src/**/*.spec.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'lcov', 'html'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.spec.ts']
},
typecheck: {
enabled: true
}
}
});
package.json Scripts
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest",
"test:coverage": "vitest run --coverage",
"test:typecheck": "vitest typecheck"
}
}
Basic Test
import { describe, it, expect } from 'vitest';
import { add } from './math';
describe('add', () => {
it('adds two numbers', () => {
expect(add(1, 2)).toBe(3);
});
});
Vitest Features
Vitest Features
Snapshot Testing
import { describe, it, expect } from 'vitest';
interface UserProfile {
name: string;
email: string;
settings: Record<string, unknown>;
}
function formatUser(user: UserProfile): string {
return `${user.name} <${user.email}>`;
}
describe('formatUser', () => {
it('formats user correctly', () => {
const user: UserProfile = {
name: 'Alice',
email: 'alice@test.com',
settings: {}
};
expect(formatUser(user)).matchSnapshot();
});
it('matches inline snapshot', () => {
expect(formatUser({
name: 'Bob',
email: 'bob@test.com',
settings: {}
})).toMatchInlineSnapshot(`"Bob <bob@test.com>"`);
});
});
Mocking
import { describe, it, expect, vi, mock } from 'vitest';
// Mock a module
vi.mock('./database.js', () => ({
db: {
query: vi.fn().mockResolvedValue([{ id: 1, name: 'Alice' }])
}
}));
import { db } from './database.js';
// Mock a function
const mockFetch = vi.fn<Parameters<typeof fetch>, ReturnType<typeof fetch>>();
// Spy on console
const consoleSpy = vi.spyOn(console, 'log');
// Restore all mocks
afterEach(() => {
vi.restoreAllMocks();
});
Fake Timers
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
describe('debounce', () => {
beforeEach(() => {
vi.useFakeTimers();
});
afterEach(() => {
vi.useRealTimers();
});
it('should debounce function calls', () => {
const fn = vi.fn();
const debounced = debounce(fn, 300);
debounced();
debounced();
debounced();
expect(fn).not.toHaveBeenCalled();
vi.advanceTimersByTime(300);
expect(fn).toHaveBeenCalledTimes(1);
});
});
Type-Safe Testing
Type-Safe Testing
Type Checking in Tests
import { describe, it, expect, expectTypeOf } from 'vitest';
function processInput(input: string | number): string {
return typeof input === 'string' ? input.toUpperCase() : input.toString();
}
describe('processInput', () => {
it('returns string for string input', () => {
const result = processInput('hello');
expectTypeOf(result).toBeString();
});
it('returns string for number input', () => {
const result = processInput(42);
expectTypeOf(result).toBeString();
});
});
// Generic function testing
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
describe('first', () => {
it('returns correct type', () => {
const result = first([1, 2, 3]);
expectTypeOf(result).toEqualTypeOf<number | undefined>();
});
});
Testing Async Code
import { describe, it, expect } from 'vitest';
async function fetchUser(id: string): Promise<{ id: string; name: string }> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
describe('fetchUser', () => {
it('returns user data', async () => {
const mockData = { id: '1', name: 'Alice' };
global.fetch = vi.fn().mockResolvedValue({
json: () => Promise.resolve(mockData)
});
const user = await fetchUser('1');
expect(user).toEqual(mockData);
});
});
Best Practices
- Use
expectTypeOffor compile-time type assertions - Vitest is faster than Jest for Vite projects
- Use
vi.mock()for module mocking - Use
vi.fn()for typed function mocks - Enable
typecheck.enabledfor type safety
Vitest vs Jest
Vitest vs Jest
Performance Comparison
| Feature | Vitest | Jest |
|---|---|---|
| Speed | Faster (Vite-based) | Slower |
| ESM Support | Native | Partial |
| TypeScript | Built-in | Requires ts-jest |
| Watch Mode | Instant HMR | File-based |
| Compatibility | Vitest API | Jest API |
| Config | vitest.config.ts | jest.config.ts |
Migration from Jest
// Jest API works in Vitest - minimal changes needed
// Before (Jest)
import { describe, it, expect, jest } from '@jest/globals';
// After (Vitest) - just change import source
import { describe, it, expect, vi } from 'vitest';
// jest.fn() becomes vi.fn()
const mockFn = vi.fn();
// jest.mock() becomes vi.mock()
vi.mock('./module.js');
When to Choose Vitest
// Vitest is ideal when:
// 1. Using Vite as your bundler
// 2. You need native ESM support
// 3. You want faster test execution
// 4. You need typecheck in tests
// Jest is ideal when:
// 1. You have an existing Jest setup
// 2. You need extensive community plugins
// 3. You use Create React App (built-in Jest)
// 4. You need older Node.js support
Shared Test Utilities
// utils/test-utils.ts - works with both Vitest and Jest
export function createMockUser(overrides?: Partial<User>): User {
return {
id: '1',
name: 'Test User',
email: 'test@example.com',
...overrides
};
}
export function expectToBeDefined<T>(value: T | null | undefined): asserts value is T {
expect(value).toBeDefined();
expect(value).not.toBeNull();
}