Vitest Core Knowledge
Deep Knowledge: Use mcp__documentation__fetch_docs with technology: vitest for comprehensive documentation.
When NOT to Use This Skill
-
E2E Testing - Use playwright or cypress for browser-based end-to-end tests
-
React Component Testing - Combine this skill with testing-library for component tests
-
Jest Migration - If project uses Jest, use jest skill instead (syntax is similar but not identical)
-
API Integration Tests - Use framework-specific integration test skills (spring-boot-integration, etc.)
Basic Test
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { calculateTotal } from './utils';
describe('calculateTotal', () => { it('should sum array of numbers', () => { expect(calculateTotal([1, 2, 3])).toBe(6); });
it('should return 0 for empty array', () => { expect(calculateTotal([])).toBe(0); }); });
Matchers
// Equality expect(value).toBe(exact); expect(value).toEqual(deepEqual); expect(value).toMatchObject(partial);
// Truthiness expect(value).toBeTruthy(); expect(value).toBeFalsy(); expect(value).toBeNull(); expect(value).toBeDefined();
// Numbers expect(value).toBeGreaterThan(n); expect(value).toBeCloseTo(0.3, 5);
// Strings expect(str).toMatch(/pattern/); expect(str).toContain('substring');
// Arrays expect(arr).toContain(item); expect(arr).toHaveLength(3);
// Errors expect(() => fn()).toThrow(); expect(() => fn()).toThrowError('message');
Mocking
// Mock function const mockFn = vi.fn(); mockFn.mockReturnValue(42); mockFn.mockResolvedValue({ data: [] });
// Mock module vi.mock('./api', () => ({ fetchUsers: vi.fn().mockResolvedValue([]) }));
// Spy const spy = vi.spyOn(object, 'method'); expect(spy).toHaveBeenCalledWith('arg');
// Reset vi.clearAllMocks(); vi.resetAllMocks();
Async Tests
it('should fetch data', async () => { const data = await fetchUsers(); expect(data).toHaveLength(3); });
it('should reject', async () => { await expect(fetchFail()).rejects.toThrow('Error'); });
Config
// vitest.config.ts export default defineConfig({ test: { globals: true, environment: 'jsdom', coverage: { provider: 'v8', reporter: ['text', 'html'] } } });
Production Readiness
Test Organization
// Proper test structure describe('UserService', () => { // Setup/teardown at appropriate level let service: UserService; let mockDb: MockedObject<Database>;
beforeAll(async () => { // Expensive setup once await setupTestDatabase(); });
beforeEach(() => { // Reset state before each test mockDb = vi.mocked(new Database()); service = new UserService(mockDb); vi.clearAllMocks(); });
afterAll(async () => { // Cleanup await teardownTestDatabase(); });
describe('create', () => { it('should create user with valid data', async () => { // Arrange const input = { name: 'John', email: 'john@example.com' }; mockDb.insert.mockResolvedValue({ id: '1', ...input });
// Act
const result = await service.create(input);
// Assert
expect(result).toMatchObject(input);
expect(mockDb.insert).toHaveBeenCalledWith('users', input);
});
it('should throw on duplicate email', async () => {
mockDb.insert.mockRejectedValue(new UniqueConstraintError());
await expect(service.create(input)).rejects.toThrow('Email already exists');
});
}); });
Coverage Configuration
// vitest.config.ts export default defineConfig({ test: { coverage: { provider: 'v8', reporter: ['text', 'html', 'lcov'], exclude: [ 'node_modules/', 'test/', '/*.d.ts', '/.config.', '/types/', ], thresholds: { lines: 80, functions: 80, branches: 75, statements: 80, }, }, }, });
CI Configuration
GitHub Actions
-
name: Run tests run: npm run test:ci
-
name: Upload coverage uses: codecov/codecov-action@v3 with: files: ./coverage/lcov.info fail_ci_if_error: true
// package.json scripts { "scripts": { "test": "vitest", "test:ci": "vitest run --coverage --reporter=junit --outputFile=test-results.xml", "test:watch": "vitest --watch" } }
Monitoring Metrics
Metric Target
Line coverage
80%
Branch coverage
75%
Test execution time < 60s
Flaky test rate 0%
Checklist
-
Arrange-Act-Assert pattern
-
Isolated tests (no shared state)
-
Meaningful test descriptions
-
Coverage thresholds enforced
-
CI/CD integration
-
No console.log in tests
-
Mocks reset between tests
-
Async tests properly awaited
-
Edge cases covered
-
Error paths tested
Anti-Patterns
Anti-Pattern Why It's Bad Solution
Testing implementation details Tests break on refactor Test behavior, not internals
Sharing state between tests Flaky, order-dependent tests Use beforeEach, isolated setup
Arbitrary waits (setTimeout) Slow, unreliable tests Use waitFor or async utilities
Not resetting mocks Previous test affects next vi.clearAllMocks() in beforeEach
Testing private methods Tight coupling to implementation Test through public API
One giant test Hard to debug failures One assertion per test (ideally)
No error case tests Production bugs slip through Test happy path AND error paths
Quick Troubleshooting
Problem Likely Cause Solution
"Cannot find module" Missing mock setup Check vi.mock() path matches import
Test timeout Async not awaited Ensure all async operations use await
"Expected 0 calls, received 1" Mock not cleared Add vi.clearAllMocks() in beforeEach
Flaky tests Shared state or timing Isolate setup, avoid setTimeout
Coverage not updating Cache issue Run with --no-cache flag
"TypeError: vi.fn is not a function" Missing import Import { vi } from 'vitest'
Reference Documentation
-
Mocking
-
Coverage