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.From its SKILL.md
npx -y skills add gruckion/marathon-ralph --skill setup-vitestAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 18 stars18 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
8.0 KB, as published. Nobody here has run it
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), '[email protected]')
await user.type(screen.getByLabelText(/password/i), 'password123')
await user.click(screen.getByRole('button', { name: /sign in/i }))
expect(onSubmit).toHaveBeenCalledWith({
email: '[email protected]',
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