iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

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…