Testing with Vitest in 2026
Vitest patterns, mocking, browser mode, and coverage that work in real projects.
Configuration Essentials
``ts
// vitest.config.ts
export default {
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./test/setup.ts'],
coverage: { provider: 'v8', reporter: ['text', 'lcov'] }
}
};
`
V8 coverage is faster than istanbul and accurate enough for most reports.
Mocking Modules
`ts
import { vi } from 'vitest';
vi.mock('./api', () => ({
fetchUser: vi.fn(async () => ({ id: 1, name: 'Ada' }))
}));
`
vi.mock is hoisted to the top of the file, like Jest. Use vi.doMock inside tests for non-hoisted mocking.
Spy on Methods
`ts
const spy = vi.spyOn(console, 'error').mockImplementation(() => {});
// run code
expect(spy).toHaveBeenCalledWith(expect.stringContaining('expected'));
spy.mockRestore();
`
Browser Mode
Vitest browser mode runs tests in real Chromium/Firefox/Webkit:
`ts
test: { browser: { enabled: true, name: 'chromium' } }
`
For component tests, this catches issues jsdom misses (CSS layout, real event timing). Slower than jsdom, so use for the layer that needs it.
Snapshots
`ts
expect(component).toMatchInlineSnapshot();
`
Inline snapshots live next to the assertion. Easier to review than out-of-line .snap files.
Concurrent Tests
`ts
describe.concurrent('parallel', () => {
test('a', async ({ expect }) => { /* */ });
test('b', async ({ expect }) => { /* */ });
});
`
Cuts runtime when tests are independent.
Test Filtering
- vitest run path/to/spec — single file
- vitest -t "name" — by test name
- vitest --changed — only files affected by git changes
Coverage Thresholds
`ts
coverage: {
thresholds: { lines: 80, functions: 80, branches: 75, statements: 80 }
}
``
Fails CI when coverage drops, preventing slow erosion.
For TypeScript-friendly assertions see [typescript generics mastery](/blog/typescript-generics-mastery).