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
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