Part 47: Testing Components with Vitest & React Testing Library

Part 47 Technology Updated September 12, 2026

Part 47: Testing Components with Vitest & React Testing Library

Testing ensures that your components behave correctly when users interact with them and prevents regression bugs. Modern React testing centers around Vitest and React Testing Library (RTL).


1. Guiding Philosophy of React Testing Library

"The more your tests resemble the way your software is used, the more confidence they can give you." — Kent C. Dodds

Instead of testing internal component state or implementation details, RTL tests how the UI looks and responds to actual user actions (clicks, typing).


2. Setup

npm install -D vitest @testing-library/react @testing-library/user-event jsdom

3. Writing a Complete Component Test (Counter.test.jsx)

Component:

import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2 data-testid="count-display">Count: {count}</h2>
      <button onClick={() => setCount(count + 1)}>Increment</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

Test Suite:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect } from 'vitest';
import Counter from './Counter';

describe('Counter Component', () => {
  it('renders with an initial count of 0', () => {
    render(<Counter />);
    expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
  });

  it('increments the count when Increment button is clicked', async () => {
    const user = userEvent.setup();
    render(<Counter />);

    const incrementButton = screen.getByRole('button', { name: /increment/i });
    await user.click(incrementButton);

    expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
  });

  it('resets the count when Reset button is clicked', async () => {
    const user = userEvent.setup();
    render(<Counter />);

    const incrementButton = screen.getByRole('button', { name: /increment/i });
    const resetButton = screen.getByRole('button', { name: /reset/i });

    await user.click(incrementButton);
    await user.click(incrementButton);
    expect(screen.getByText(/count: 2/i)).toBeInTheDocument();

    await user.click(resetButton);
    expect(screen.getByText(/count: 0/i)).toBeInTheDocument();
  });
});

Key Takeaways

  • Use accessible queries (getByRole, getByLabelText, getByText) rather than test IDs where possible.
  • Use @testing-library/user-event to simulate realistic browser interactions.
  • Test user-visible outcomes, not internal state variables.

In Part 48, we learn Mocking APIs with MSW (Mock Service Worker)!