React Component Testing Best Practices

When I first started building React apps years ago, I often skipped writing tests. It felt faster to just build features and test them manually in the browser.

But as my projects grew, I quickly realized that skipping tests led to bugs, regressions, and late-night debugging sessions. That’s when I committed to learning React component testing properly.

Over time, I’ve refined a set of best practices that help me write tests that are reliable, maintainable, and easy to understand. In this tutorial, I’ll walk you through these practices step by step with full working code examples.

Method 1 – Test Components with React Testing Library (RTL)

React Testing Library (RTL) is my go-to tool for testing React components. It focuses on testing how users interact with your app, not the internal implementation.

// ButtonCounter.js
import React, { useState } from "react";

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

  return (
    <div>
      <p data-testid="count-text">Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
// ButtonCounter.test.js
import { render, screen, fireEvent } from "@testing-library/react";
import ButtonCounter from "./ButtonCounter";

test("increments counter when button is clicked", () => {
  render(<ButtonCounter />);
  const button = screen.getByText(/Increment/i);
  fireEvent.click(button);
  expect(screen.getByTestId("count-text")).toHaveTextContent("Count: 1");
});

You can refer to the screenshot below to see the output.

React Component Testing

This test checks the user’s perspective: clicking the button updates the count. Notice we didn’t test the internal state; instead, we verified the visible output.

Method 2 – Use Jest for Snapshot Testing

Snapshot testing is great for catching unexpected UI changes. I use it when I want to ensure a component renders consistently.

Here’s an example with a UserCard component:

// UserCard.js
import React from "react";

export default function UserCard({ name, email }) {
  return (
    <div className="user-card">
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
}
// UserCard.test.js
import { render } from "@testing-library/react";
import UserCard from "./UserCard";

test("renders user card snapshot", () => {
  const { asFragment } = render(<UserCard name="John Doe" email="[email protected]" />);
  expect(asFragment()).toMatchSnapshot();
});

You can refer to the screenshot below to see the output.

React Component Testing Best Practices

The snapshot test saves the rendered output in a file. If the component changes, Jest will alert you to review and update the snapshot.

Method 3 – Mock API Calls in Tests

Most real-world apps in the USA rely on APIs, whether for fetching weather, stock prices, or user data. In tests, I never want to hit the real API. Instead, I mock the API.

Here’s an example with a UserProfile component fetching data:

// UserProfile.js
import React, { useEffect, useState } from "react";

export default function UserProfile({ userId }) {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then((res) => res.json())
      .then((data) => setUser(data));
  }, [userId]);

  if (!user) return <p>Loading...</p>;

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  );
}
// UserProfile.test.js
import { render, screen, waitFor } from "@testing-library/react";
import UserProfile from "./UserProfile";

beforeEach(() => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ name: "Alice Johnson", email: "[email protected]" }),
    })
  );
});

test("renders user profile from API", async () => {
  render(<UserProfile userId={1} />);
  await waitFor(() => screen.getByText("Alice Johnson"));
  expect(screen.getByText("Alice Johnson")).toBeInTheDocument();
  expect(screen.getByText("[email protected]")).toBeInTheDocument();
});

By mocking fetch, the test doesn’t depend on an external server. This makes the test fast, stable, and predictable.

Method 4 – Test Form Inputs and Validation

Forms are everywhere: sign-ups, logins, surveys. I always test forms by simulating real user input.

Here’s a LoginForm example:

// LoginForm.js
import React, { useState } from "react";

export default function LoginForm({ onLogin }) {
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    if (email && password) {
      onLogin({ email, password });
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        type="email"
        placeholder="Email"
        value={email}
        onChange={(e) => setEmail(e.target.value)}
      />
      <input
        type="password"
        placeholder="Password"
        value={password}
        onChange={(e) => setPassword(e.target.value)}
      />
      <button type="submit">Login</button>
    </form>
  );
}
// LoginForm.test.js
import { render, screen, fireEvent } from "@testing-library/react";
import LoginForm from "./LoginForm";

test("submits form with email and password", () => {
  const mockLogin = jest.fn();
  render(<LoginForm onLogin={mockLogin} />);

  fireEvent.change(screen.getByPlaceholderText(/Email/i), {
    target: { value: "[email protected]" },
  });
  fireEvent.change(screen.getByPlaceholderText(/Password/i), {
    target: { value: "mypassword" },
  });
  fireEvent.click(screen.getByText(/Login/i));

  expect(mockLogin).toHaveBeenCalledWith({
    email: "[email protected]",
    password: "mypassword",
  });
});

You can refer to the screenshot below to see the output.

Component Testing in React Best Practices

This test ensures the form submits correctly when valid input is provided. It mimics a real user typing and clicking.

Method 5 – Accessibility Testing with Jest-axe

Accessibility is critical, especially in the USA, where compliance with the ADA (Americans with Disabilities Act) is important.
I use jest-axe to catch accessibility issues automatically.

npm install --save-dev jest-axe
// AccessibleButton.js
import React from "react";

export default function AccessibleButton({ label }) {
  return <button aria-label={label}>{label}</button>;
}
// AccessibleButton.test.js
import { render } from "@testing-library/react";
import { axe } from "jest-axe";
import AccessibleButton from "./AccessibleButton";

test("button should have no accessibility violations", async () => {
  const { container } = render(<AccessibleButton label="Submit Form" />);
  const results = await axe(container);
  expect(results).toHaveNoViolations();
});

This test ensures the button meets accessibility standards. It’s a simple way to bake accessibility into your workflow.

Key Best Practices I Follow

  1. Test behavior, not implementation – focus on what the user sees.
  2. Keep tests independent – don’t rely on other tests’ data.
  3. Use descriptive test names – they should read like plain English.
  4. Mock external dependencies – APIs, localStorage, etc.
  5. Run tests in CI/CD pipelines – so bugs are caught before production.

When I look back, adopting these testing practices has saved me countless hours. I’ve avoided regressions, shipped features faster, and built apps that users trust.

If you’re working on a React project today, I strongly recommend starting with React Testing Library and Jest. Over time, you can layer in mocking, accessibility checks, and snapshot tests to build a rock-solid testing suite.

I hope you found this guide useful. If you have questions or want me to expand on a specific method, feel free to reach out.

You may also like to read:

Leave a Comment

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.