DEV Community

Alex Spinov
Alex Spinov

Posted on

Testing Library Has Free Tools That Replace Enzyme — Here's the Migration Guide

If you're still using Enzyme for React testing in 2026, it's time to switch. Testing Library has become the standard — and here's why.

Why Testing Library Won

Testing Library tests your components the way users actually interact with them. No shallow rendering, no implementation details, no brittle tests that break on refactors.

Quick Start

bun add -d @testing-library/react @testing-library/jest-dom @testing-library/user-event
Enter fullscreen mode Exit fullscreen mode
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { LoginForm } from "./LoginForm";

test("submits login form", async () => {
  const onSubmit = vi.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  await userEvent.type(screen.getByLabelText("Email"), "alice@example.com");
  await userEvent.type(screen.getByLabelText("Password"), "secret123");
  await userEvent.click(screen.getByRole("button", { name: "Sign In" }));

  expect(onSubmit).toHaveBeenCalledWith({
    email: "alice@example.com",
    password: "secret123",
  });
});
Enter fullscreen mode Exit fullscreen mode

Core Queries (Pick the Right One)

Priority Order (use top ones first):

  1. getByRole — best for accessibility
screen.getByRole("button", { name: "Submit" });
screen.getByRole("heading", { level: 2 });
screen.getByRole("textbox", { name: "Email" });
Enter fullscreen mode Exit fullscreen mode
  1. getByLabelText — for form fields
screen.getByLabelText("Password");
Enter fullscreen mode Exit fullscreen mode
  1. getByPlaceholderText — fallback for forms
screen.getByPlaceholderText("Search...");
Enter fullscreen mode Exit fullscreen mode
  1. getByText — for non-interactive elements
screen.getByText("Welcome back!");
screen.getByText(/total: \$\d+/i); // regex works too
Enter fullscreen mode Exit fullscreen mode
  1. getByTestId — last resort
screen.getByTestId("custom-element");
Enter fullscreen mode Exit fullscreen mode

Testing Async Behavior

test("loads user data", async () => {
  render(<UserProfile userId="123" />);

  // Wait for loading to finish
  expect(screen.getByText("Loading...")).toBeInTheDocument();

  // findBy* waits automatically (up to 1000ms)
  const userName = await screen.findByText("Alice Johnson");
  expect(userName).toBeInTheDocument();

  // Verify loading is gone
  expect(screen.queryByText("Loading...")).not.toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

userEvent vs fireEvent

// BAD: fireEvent dispatches a single DOM event
fireEvent.click(button); // Just one click event

// GOOD: userEvent simulates real user behavior
await userEvent.click(button); // focus → pointerdown → mousedown → pointerup → mouseup → click
await userEvent.type(input, "hello"); // focus → keydown → keypress → input → keyup (per char)
Enter fullscreen mode Exit fullscreen mode

Always prefer userEvent — it catches more bugs because it simulates real browser behavior.

Common Patterns

Testing Modals

test("opens and closes modal", async () => {
  render(<App />);

  await userEvent.click(screen.getByText("Open Settings"));
  expect(screen.getByRole("dialog")).toBeInTheDocument();

  await userEvent.click(screen.getByRole("button", { name: "Close" }));
  expect(screen.queryByRole("dialog")).not.toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Testing API Calls

test("displays fetched data", async () => {
  server.use(
    http.get("/api/users", () => {
      return HttpResponse.json([{ id: 1, name: "Alice" }]);
    })
  );

  render(<UserList />);

  const user = await screen.findByText("Alice");
  expect(user).toBeInTheDocument();
});
Enter fullscreen mode Exit fullscreen mode

Testing Error States

test("shows error on failed fetch", async () => {
  server.use(
    http.get("/api/users", () => {
      return new HttpResponse(null, { status: 500 });
    })
  );

  render(<UserList />);

  const error = await screen.findByRole("alert");
  expect(error).toHaveTextContent("Failed to load users");
});
Enter fullscreen mode Exit fullscreen mode

Migration from Enzyme Cheat Sheet

Enzyme Testing Library
shallow() render() (no shallow!)
wrapper.find(".class") screen.getByRole()
wrapper.instance() Not needed (test behavior)
wrapper.state() Not needed (test output)
wrapper.setProps() rerender(<Comp newProp />)
wrapper.simulate("click") await userEvent.click()

Need to test web scraping output? Check out my ready-to-use scraping actors on Apify Store — validated data extraction without writing code. For custom solutions, email spinov001@gmail.com.

Top comments (0)