Agent skill
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.
Install this agent skill to your Project
npx add-skill https://github.com/gruckion/marathon-ralph/tree/main/skills/setup-vitest
SKILL.md
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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
project-detection
Detects project type, package manager, and monorepo structure. Returns correct commands for test/build/lint/dev. Run at project initialization and cache results in state. Use before running any build/test commands.
setup-playwright
Configure Playwright for E2E testing. Use when setting up end-to-end tests, when no E2E framework is detected, or when the user asks to configure browser testing.
update-state
Programmatically update marathon-ralph state file using deterministic jq commands. Use this instead of manually editing the JSON file.
write-playwright-test
Write Playwright E2E tests using fixtures and best practices. Use when creating E2E tests, writing browser automation tests, or testing user flows.
visual-verification
Visually verify implemented features work correctly before marking complete. Use when testing UI changes, verifying web features, or checking user flows work in the browser.
sqlite
Didn't find tool you were looking for?