Testing React Components with Vitest and React Testing Library
React Testing Library encourages tests that interact with your UI the way users do: find a button by its label, click it, check what appears. Vitest runs those tests quickly and shares config with Vite.
Setup
npm install -D vitest jsdom @testing-library/react @testing-library/user-event @testing-library/jest-dom
// vite.config.js
export default defineConfig({
plugins: [react()],
test: { environment: 'jsdom', setupFiles: './src/test/setup.js', globals: true },
});
// src/test/setup.js
import '@testing-library/jest-dom/vitest';
A first test
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import Counter from './Counter';
test('increments when clicked', async () => {
const user = userEvent.setup();
render(<Counter />);
await user.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('Count: 1')).toBeInTheDocument();
});
Async UI and mocked requests
import { vi } from 'vitest';
test('shows users from the API', async () => {
vi.spyOn(globalThis, 'fetch').mockResolvedValue(
new Response(JSON.stringify([{ id: 1, name: 'Asha' }]))
);
render(<UserList />);
expect(await screen.findByText('Asha')).toBeInTheDocument(); // findBy waits
});
Queries in order of preference
getByRole(withname) - mirrors how assistive technology sees the page.getByLabelText- form fields.getByText- non-interactive content.getByTestId- last resort.
Tips
- Use
findBy*for things that appear after async work andqueryBy*to assert something is absent. - Test behaviour, not implementation details like state variable names.
- For network-heavy apps, Mock Service Worker (MSW) gives realistic API mocks shared by tests and the browser.
Was this article helpful?
Comments
No comments yet. Have a question or a tip? Be the first to share it.