Testing (RTL + Vitest)
React testing in 2026 - Vitest for unit + component, React Testing Library for behaviour, Playwright for E2E.
React testing stack
EXAMPLE
// 1. Vitest + React Testing Library
// npm i -D vitest @vitest/ui jsdom @testing-library/react @testing-library/jest-dom @testing-library/user-event
// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./test/setup.ts'],
coverage: { provider: 'v8', reporter: ['text', 'html'] },
},
});
// test/setup.ts
import '@testing-library/jest-dom/vitest';
// 2. Component test - test behaviour, not implementation
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';
it('increments on click', async () => {
render(<Counter initial={0} />);
const btn = screen.getByRole('button', { name: /count: 0/i });
await userEvent.click(btn);
expect(screen.getByRole('button', { name: /count: 1/i })).toBeInTheDocument();
});
// 3. Async + MSW for network
// npm i -D msw
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users/:id', ({ params }) =>
HttpResponse.json({ id: params.id, name: 'Ada' })
)
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
it('shows user', async () => {
render(<UserCard id='1' />);
expect(await screen.findByText('Ada')).toBeInTheDocument();
});
// 4. Playwright for E2E
// npm i -D @playwright/test
// npx playwright install
import { test, expect } from '@playwright/test';
test('signup flow', async ({ page }) => {
await page.goto('/signup');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('Sup3rSecret!');
await page.getByRole('button', { name: 'Create account' }).click();
await expect(page).toHaveURL('/onboarding');
});
// 5. Visual regression
// Add @playwright/test's toHaveScreenshot + check in baseline images
// Coverage targets - sane defaults
// - 80 percent for components with logic
// - 100 percent for pure utility functions
// - skip for thin presentational components
Why it matters
Test behaviour, not implementation. RTL forces it by querying like a user. MSW for network so tests stay deterministic. Playwright for E2E gives you the same APIs across browsers. Visual regression catches what assertions miss.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
import { render, screen } from '@testing-library/react';
import { test, expect } from 'vitest';
test('greets', () => {
render(<Greet name="Ada" />);
expect(screen.getByText(/Ada/)).toBeInTheDocument();
});
Try it Yourself »
Discussion
Loading…