Professional React WorkflowPro· 45 min read

Testing React: Jest & React Testing Library

Write automated tests that click your components like a real user and check what shows on screen.

What you will learn

  • Explain what a component test checks
  • Render a component and query it like a user
  • Simulate a click and assert the result

Why test components?

A test is a small piece of code that runs your component automatically and checks it behaves correctly — so you catch breakages without clicking through the app by hand every time. Two tools work together: Jest (or Vitest in Vite projects), the test runner that finds and runs your tests; and React Testing Library (RTL), which renders a component and lets you query it the way a user would — by the text and labels they see, not by internal details.

RTL’s golden rule: test what the user sees and does, not how the component is built inside. That way your tests keep passing when you refactor, and only fail when behaviour actually breaks.

Setup

Install a test runner and React Testing Library (Vite projects)
npm install -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom

These dev-dependencies give you the runner (vitest, used just like Jest), RTL to render components, jest-dom for friendly checks like “is this on screen”, user-event to simulate real clicks and typing, and jsdom (a fake browser environment so tests can run without a real browser). This is real project code, shown with its Output.

The component under test

A simple component we want to test
// Counter.jsx
import { useState } from 'react';

export function Counter() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

This Counter component shows a count and an Increment button that adds 1 on each click — intentionally tiny so the test is easy to follow. We will check two behaviours: that it first renders Count: 0, and that clicking the button updates the number to 1.

The test

Render, query like a user, click, assert
// Counter.test.jsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter.jsx';

test('starts at 0 and increments when clicked', async () => {
  render(<Counter />);

  // 1) it starts at 0 (find the text the user sees)
  expect(screen.getByText('Count: 0')).toBeInTheDocument();

  // 2) click the button by its visible label
  await userEvent.click(screen.getByRole('button', { name: 'Increment' }));

  // 3) the count is now 1
  expect(screen.getByText('Count: 1')).toBeInTheDocument();
});

A test reads like a story of a user interacting with the component:

  1. Render it: render(<Counter />) mounts the component into the fake browser so we can interact with it.
  2. Query like a user: screen.getByText('Count: 0') finds the element by the text a person would read. expect(...).toBeInTheDocument() is the assertion — it says “I expect this to be on screen”. If it is not, the test fails.
  3. Act: userEvent.click(screen.getByRole('button', { name: 'Increment' })) finds the button by its visible label and clicks it, exactly like a user would. We await it because the click is asynchronous.
  4. Assert the result: after the click we expect Count: 1 to be on screen. If the component is broken, this line fails and tells you.

Note: Output (from the test runner): ✓ starts at 0 and increments when clicked (23 ms) Test Files 1 passed (1) Tests 1 passed (1) If the increment logic were broken, you would instead see a red ✗ with “Unable to find an element with the text: Count: 1”.

How to query — prefer what users perceive

QueryFinds elements by
getByRole('button', { name })Role + visible label (preferred)
getByText('…')Visible text content
getByLabelText('…')A form field by its <label>
getByTestId('…')A data-testid (last resort)

Tip: Beyond unit/component tests, larger teams add end-to-end (E2E) tests with Playwright or Cypress — these drive a real browser through whole user journeys (log in, add to cart, check out). Start with RTL component tests; reach for E2E for critical flows.

Watch out: Avoid testing internal details (state variable names, exact CSS classes). Test the behaviour the user experiences. Detail-coupled tests break on every refactor and slow the team down.

Q. What is React Testing Library’s core philosophy?

Answer: RTL encourages querying by role, label and text — the way a user perceives the UI — so tests reflect real behaviour and survive refactors.

✍️ Practice

  1. Write a test that renders a Greeting component and asserts the greeting text is on screen.
  2. Write a test that types into a controlled input and asserts a live preview updates.

🏠 Homework

  1. Add a test for your to-do app: render it, add a task by clicking Add, and assert the new task appears in the list.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →