This article is published in English.
React component tests with Vitest and Testing Library
Wire Vitest into Vite, colocate behavior tests, run watch vs CI, and brief an AI agent to follow a red-green verify loop.
Vitest is a fast, Vite-native test runner that feels familiar if you know Jest — and pairs cleanly with React Testing Library for component behavior tests. This walkthrough covers setup, a practical red-green loop, file layout, and how to brief an AI coding agent so it helps instead of thrashing.
1. Project setup
Install the usual React testing stack as development dependencies:
npm install -D vitest jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
For a Vite React project, teach Vitest about the app inside vite.config.ts so aliases and plugins match production builds:
For a Vite React project, add Vitest to vite.config.ts:
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
setupFiles: ['./src/test/setup.ts'],
globals: true,
},
})
environment: 'jsdom' gives DOM APIs without a real browser. A small setup file registers jest-dom matchers once for every suite:
import '@testing-library/jest-dom/vitest'
Wire that file through Vitest’s setupFiles option so assertions like toBeInTheDocument and toHaveAccessibleName work everywhere.
2. Recommended workflow
Use a tight loop: Feature → Test → Implement → Verify → Refactor.
- Understand the feature — list user-visible behavior and important states (loading, success, error, empty, disabled). Note API or browser boundaries that need mocks so tests stay deterministic.
- Write the test first — prefer React Testing Library queries; assert outcomes users notice, not private state; drive clicks and typing with
userEventfor realism. - Implement the minimum — keep production code focused on satisfying the failing test; avoid test-only hooks or leaked internals unless there is no cleaner seam.
- Run targeted tests while iterating so feedback stays seconds, not minutes:
npx vitest run src/components/Button.test.tsx
During development:
Watch mode for the local feedback loop:
npx vitest
Run the full suite
Full suite via the npm script your package.json exposes:
npm test
or a one-shot CI-style run without watchers:
npx vitest run
Check coverage when appropriate
Coverage when you need a spotlight on untested branches:
npx vitest run --coverage
Treat coverage as a guide, not a vanity percentage — prefer meaningful assertions over chasing 100%.
3. Test structure
Colocate tests with the components they protect:
src/
├── components/
│ ├── LoginForm.tsx
│ └── LoginForm.test.tsx
├── hooks/
│ ├── useUser.ts
│ └── useUser.test.ts
├── pages/
│ ├── Dashboard.tsx
│ └── Dashboard.test.tsx
└── test/
├── setup.ts
└── mocks/
For a component, structure tests around behavior:
Shape cases around user outcomes rather than method names:
describe('LoginForm', () => {
it('allows a user to submit valid credentials', async () => {
// arrange
// act
// assert
})
it('shows validation errors for invalid input', async () => {
// ...
}) it('disables submission while logging in', async () => {
// ...
})
})
Cover the happy path, validation failures, and disabled or loading states in separate it blocks. When something breaks, the failing title should name one behavior. Share arrange helpers sparingly; duplicated setup that reads clearly beats clever fixtures that hide intent.
4. Working with an AI coding agent
4. Vitest-agent prompt
Brief the agent as a senior React testing engineer: write or update the failing test first, implement the smallest change, then verify with Vitest and typecheck:
npx vitest run <target-test>
npx vitest run
npm run typecheck
npm run lint
Ask it to prefer Testing Library queries (getByRole, findByText) over brittle CSS selectors, to mock network boundaries instead of reaching into component state, and to leave a short note when a test documents an intentional product constraint.
Closing habits
Keep Vitest config shared with Vite so path aliases and plugins match production. Prefer run in CI and watch locally. Expand suites as features land, not in a big-bang rewrite. With that loop — test first, implement, verify — React apps stay refactorable without slowing the Vite-powered feedback cycle that made Vitest attractive in the first place.