setup-vitest
Configure Vitest for unit and integration testing. Use when setting up a test framework, when no test runner is detected, or when the user asks to configure testing.
When & Why to Use This Skill
This Claude skill automates the end-to-end configuration of Vitest for modern web development projects. It streamlines the setup of unit and integration testing environments by handling dependency installation, generating optimized configuration files, and integrating essential tools like Testing Library and c8/v8 for code coverage. Designed for both standalone projects and complex monorepos, it ensures a robust, high-performance testing foundation following industry best practices.
Use Cases
- Initial Setup: Automatically configure Vitest in a new project where no test runner is currently detected.
- Framework Integration: Set up framework-specific testing environments for React, Vue, or Svelte using Testing Library and jsdom.
- Migration: Transition existing test suites from Jest to Vitest to take advantage of faster execution and Vite-native integration.
- Monorepo Orchestration: Configure unified testing pipelines across multiple workspaces using Turborepo, pnpm, or npm/yarn workspaces.
- Quality Assurance: Establish automated code coverage thresholds and reporting to maintain high code quality standards in CI/CD pipelines.
- Mocking & Global Setup: Implement global test configurations, including window object mocking and custom setup files for complex browser-based tests.
| name | setup-vitest |
|---|---|
| description | Configure Vitest for unit and integration testing. Use when setting up a test framework, when no test runner is detected, or when the user asks to configure testing. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
Setup Vitest
Configure Vitest as the unit and integration test framework with Testing Library integration.
When to Use This Skill
- No test framework is configured in the project
- User requests to set up unit testing
- Migrating from Jest to Vitest
- Setting up a new project that needs testing
Installation
Use ni to auto-detect the package manager:
# Core Vitest packages
ni -D vitest @vitest/ui @vitest/coverage-v8
# For React projects
ni -D @testing-library/react @testing-library/dom @testing-library/user-event @testing-library/jest-dom
# For Vue projects
ni -D @testing-library/vue @testing-library/dom @testing-library/user-event @testing-library/jest-dom
# For Svelte projects
ni -D @testing-library/svelte @testing-library/dom @testing-library/user-event @testing-library/jest-dom
Configuration
vitest.config.ts
Create or update vitest.config.ts at the project root:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react' // For React projects
export default defineConfig({
plugins: [react()], // Add framework plugin as needed
test: {
// Test file patterns
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
exclude: ['**/node_modules/**', '**/dist/**', '**/e2e/**'],
// Environment - use 'jsdom' or 'happy-dom' for DOM testing
environment: 'jsdom',
// Enable global test APIs (describe, it, expect)
globals: true,
// Setup files run before each test file
setupFiles: ['./tests/setup.ts'],
// Mock behavior
clearMocks: true,
restoreMocks: true,
// Coverage configuration
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html', 'lcov'],
reportsDirectory: './coverage',
include: ['src/**/*.{ts,tsx}'],
exclude: [
'**/*.test.{ts,tsx}',
'**/*.spec.{ts,tsx}',
'**/*.d.ts',
'**/types/**',
],
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
// Timeouts
testTimeout: 5000,
hookTimeout: 10000,
},
})
TypeScript Configuration
Add Vitest types to tsconfig.json:
{
"compilerOptions": {
"types": ["vitest/globals"]
}
}
Setup File
Create tests/setup.ts for global test configuration:
import '@testing-library/jest-dom/vitest'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'
// Cleanup after each test
afterEach(() => {
cleanup()
})
// Mock window.matchMedia (common requirement)
Object.defineProperty(window, 'matchMedia', {
writable: true,
value: vi.fn().mockImplementation((query: string) => ({
matches: false,
media: query,
onchange: null,
addListener: vi.fn(),
removeListener: vi.fn(),
addEventListener: vi.fn(),
removeEventListener: vi.fn(),
dispatchEvent: vi.fn(),
})),
})
Package.json Scripts
Add test scripts to the workspace package.json (where the code lives):
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:ui": "vitest --ui",
"test:coverage": "vitest run --coverage"
}
}
Monorepo Configuration
For monorepo projects (Turborepo, Nx, Lerna, etc.), additional setup is required.
1. Check Project State
Read .claude/marathon-ralph.json to get the project configuration:
project.monorepo.type- The monorepo type (turbo, nx, lerna, etc.)project.packageManager- The package manager (bun, pnpm, yarn, npm)
2. Turborepo Setup
If using Turborepo (turbo.json exists), add the test task to the pipeline:
turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"test": {
"dependsOn": ["^build"],
"outputs": [],
"cache": false
},
"test:run": {
"dependsOn": ["^build"],
"outputs": [],
"cache": false
}
}
}
Root package.json - add script to run tests across all workspaces:
{
"scripts": {
"test": "turbo run test",
"test:run": "turbo run test:run"
}
}
3. pnpm Workspaces Setup
For pnpm workspaces without Turborepo:
Root package.json:
{
"scripts": {
"test": "pnpm -r test",
"test:run": "pnpm -r test:run"
}
}
4. npm/yarn Workspaces Setup
For npm or yarn workspaces:
Root package.json:
{
"scripts": {
"test": "npm run test --workspaces",
"test:run": "npm run test:run --workspaces"
}
}
5. Workspace-Specific Testing
To run tests for a specific workspace, use the package manager's filter:
# Turborepo + bun
bun run --filter=web test
# pnpm
pnpm --filter web test
# npm workspaces
npm run test --workspace=web
Writing Tests
Query Priority (Most Accessible First)
Follow Testing Library's query priority:
getByRole- Best choice, tests accessibilitygetByLabelText- For form fieldsgetByPlaceholderText- If no label availablegetByText- For non-interactive elementsgetByDisplayValue- For filled form valuesgetByAltText- For imagesgetByTitle- Rarely neededgetByTestId- Last resort only
Example Test
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect, vi } from 'vitest'
import { LoginForm } from './LoginForm'
describe('LoginForm', () => {
it('submits with valid credentials', async () => {
const user = userEvent.setup()
const onSubmit = vi.fn()
render(<LoginForm onSubmit={onSubmit} />)
// Use accessible queries
await user.type(screen.getByLabelText(/email/i), 'user@example.com')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(onSubmit).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'password123',
})
})
it('shows error for invalid email', async () => {
const user = userEvent.setup()
render(<LoginForm onSubmit={vi.fn()} />)
await user.type(screen.getByLabelText(/email/i), 'invalid')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(screen.getByRole('alert')).toHaveTextContent(/valid email/i)
})
})
Testing Philosophy
Follow Kent C. Dodds' testing principles:
DO
- Test user behavior, not implementation details
- Use
screenfor all queries - Prefer
getByRolewith accessible names - Use
userEventoverfireEvent - Use
findBy*for async elements - Use
queryBy*ONLY for asserting non-existence
DON'T
- Test internal state or methods
- Use
container.querySelector - Use test IDs when better queries exist
- Add unnecessary accessibility attributes
- Mock everything (test real behavior where possible)
Mocking
Mock Functions
import { vi } from 'vitest'
const mockFn = vi.fn()
mockFn.mockReturnValue('value')
mockFn.mockResolvedValue('async value')
Mock Modules
// Automatic mock
vi.mock('./api')
// Manual mock with factory
vi.mock('./api', () => ({
fetchUser: vi.fn(() => ({ id: 1, name: 'Test' })),
}))
// Partial mock
vi.mock('./utils', async (importOriginal) => {
const actual = await importOriginal()
return {
...actual,
specificFunction: vi.fn(),
}
})
Verification
After setup, verify with:
# Run tests
nr test
# Run with coverage
nr test:coverage
# Open UI mode
nr test:ui
Directory Structure
project/
├── src/
│ ├── components/
│ │ ├── Button.tsx
│ │ └── Button.test.tsx # Colocated tests
│ └── utils/
│ ├── helpers.ts
│ └── helpers.test.ts
├── tests/
│ └── setup.ts # Global setup
├── vitest.config.ts
└── package.json