How to use act() in React Testing Library?

Ensure stable and warning-free React tests with act() in React Testing Library. Learn its importance, proper usage, and best practices for handling updates and async operations.

Get Started free
Guide Banner Image
Home Guide How to use act() in React Testing Library?

How to use act() in React Testing Library?

React Testing Library (RTL) is a popular tool for testing React components. It helps you write tests that interact with components like real users, including clicking buttons, filling out forms, and checking the rendered output.

When writing tests in the React Testing Library, you might have encountered warnings like “An update to Counter inside a test was not wrapped in act().” This usually happens when React tries to update the UI, but the test isn’t properly handling those updates. That’s where act() comes in.

This article explains what act() does, why it’s needed, and how to use it effectively to avoid test warnings and ensure reliable results.

What is act() in the React Testing Library?

act() is a special helper function in the React Testing Library that ensures all updates related to a component are completed before assertions are made in a test.

React updates the UI in batches and handles them asynchronously. This means the test might check the UI before it’s fully updated, leading to unexpected results or failures. act() helps with this by making sure React finishes all updates before the test moves on.

Without act(), you might get warnings and flaky tests because your component updates aren’t being handled properly.

Why Use act() in React Testing Library?

Using act() is necessary to ensure your test cases execute as expected without warnings or flaky results. Here are some scenarios where using act() is important:

  • State Updates: When an event updates a component’s state, the test needs to wait for React to finish processing before checking the result. Here, act() ensures that changes propagate before assertions run.
  • Effect Execution: React’s useEffect runs after rendering. When effects modify the DOM or update the state, act() ensures the changes are applied before assertions.
  • Async Operations: In scenarios that involve setTimeout, API calls, or Promises, act() ensures that assertions only execute after all asynchronous updates are complete.

What happens if you don’t use act()?

If you skip using act(), your tests might behave unpredictably. Here’s what can happen:

  • If you don’t wrap updates inside act(), you might see warnings about state updates outside React’s lifecycle.
  • Your tests may pass or fail inconsistently because updates aren’t fully applied before assertions run.
  • Without act(), your test might check the state too soon before React has finished updating, leading to inconsistent results.

When to Use act() in React Testing Library?

act() is used whenever your test involves changes to component’s state, effects, or UI updates. This is important because React updates the UI asynchronously, which means changes don’t always happen instantly.

If your test checks the UI too soon, it might fail or produce unreliable results. For example, if a button click updates text on the screen, but the test checks text before React has finished processing the update, you might get an outdated value.

Using act() here ensures that React completes all updates before the test verifies the result.

Using act() in React Testing Library

When testing React components, not using act() in some situations can lead to warnings and flaky tests. Let’s go step by step to understand this better.

Here’s a simple counter component that increments the count when the button is clicked.

import { useState } from "react";




function Counter() {

  const [count, setCount] = useState(0);




  return (

    <div>

      <p>Count: {count}</p>

      <button onClick={() => setCount(count + 1)}>Increment</button>

    </div>

  );

}




export default Counter;
Copied

Example Without act()

If you write a test without act(), you might get a warning and inconsistent test results.

import { render, screen, fireEvent } from "@testing-library/react";

import Counter from "./Counter";




test("increments counter on button click", () => {

  render(<Counter />);

  

  const button = screen.getByText("Increment");

  fireEvent.click(button);




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

Running this test might produce this warning:

Warning: An update to Counter inside a test was not wrapped in act().
Copied

In this test, fireEvent.click(button) triggers a state update, but the assertion (expect(screen.getByText(“Count: 1”))) might run before React has finished processing the update. This can cause flaky tests, where sometimes the test passes, and other times it fails.

Example With act()

To fix the above warning, you can wrap the state-changing action inside the act()

import { render, screen, fireEvent } from "@testing-library/react";

import { act } from "react-dom/test-utils";

import Counter from "./Counter"; 




test("increments counter on button click", () => {

  render(<Counter />);

  

  const button = screen.getByText("Increment");




  act(() => {

    fireEvent.click(button);

  });




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

Using act(), you tell React to complete all updates before the test checks the UI. This helps keep tests reliable and prevents unexpected failures.

Handling Asynchronous Code with act()

When testing asynchronous operations like API calls, timers, or delayed UI updates, React may take some time to process changes. If your test checks the UI before these updates are complete, it can lead to flaky results.

Wrapping async actions inside act() ensures React finishes processing before the test moves forward.

Here’s an example of using act() for asynchronous updates:

import { render, screen } from "@testing-library/react";

import { act } from "react-dom/test-utils";

import Counter from "./Counter";




test("displays count after delay", async () => {

  render(<Counter />);

  

  await act(async () => {

    await new Promise((resolve) => setTimeout(resolve, 1000));

  });




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

Whenever testing asynchronous behavior, wrapping updates inside act(async () => { … }) ensures your tests wait for React to finish updating before making assertions.

Best Practices for Using act() in React Tests

To write stable and reliable tests in the React Testing Library, follow these best practices when using act():

1. Use act() for State and Effect Updates: Whenever a test triggers a state change or an effect like an API call or a timeout, wrap the action inside act(). This ensures React processes updates before the test checks the UI.

import { render, screen, fireEvent } from "@testing-library/react";

import { act } from "react-dom/test-utils";

import Counter from "./Counter";




test("increments counter on button click", () => {

  render(<Counter />);




  const button = screen.getByText("Increment");




  act(() => {

    fireEvent.click(button);

  });




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

2. Use await act() for Asynchronous Updates: For async operations like API calls, timeouts, or delayed state updates, use await act(async () => { … }).

import { render, screen } from "@testing-library/react";

import { act } from "react-dom/test-utils";

import Counter from "./Counter";




test("displays count after delay", async () => {

  render(<Counter />);




  await act(async () => {

    await new Promise((resolve) => setTimeout(resolve, 1000));

  });




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

3. Use findBy Queries for Auto-Waiting: If your test involves UI updates that take time, findBy queries in the React Testing Library automatically wait for elements to appear. This means you don’t always need to use act().

expect(await screen.findByText("Count: 1")).toBeInTheDocument();
Copied

4. Use Built-in RTL Utilities: React Testing Library’s fireEvent and userEvent often handle act() automatically. But if a warning still appears, you can manually wrap them inside act().

import { render, screen, fireEvent } from "@testing-library/react";

import { act } from "react-dom/test-utils";

import Counter from "./Counter";




test("increments counter on button click", () => {

  render(<Counter />);




  const button = screen.getByText("Increment");




  act(() => {

    fireEvent.click(button);

  });




  expect(screen.getByText("Count: 1")).toBeInTheDocument();

});
Copied

5. Avoid Overusing act(): You don’t always need to use act(). If your test doesn’t trigger state changes or effects, React Testing Library usually handles updates on its own. Only use act() when you see warnings or inconsistent test results.

Testing locally may not always reflect real user conditions. Once you’ve ensured your tests are stable with act(), it’s essential to verify that your React application works seamlessly across different environments.

With BrowserStack Live, you can test React applications on real browsers and devices, identifying UI inconsistencies that may not surface in a local setup.

BrowserStack Live Banner

Conclusion

Using act() in the React Testing Library helps keep your tests stable and reliable by ensuring React finishes all updates before checking the UI. You don’t always need it, but it’s important when working with state changes, effects, or async updates.

By following best practices like using act() when it’s necessary, using findBy for async updates and taking advantage of built-in RTL utilities, you can write cleaner and more reliable tests.

Testing your React application on real devices and browsers is essential to ensure all real user conditions are considered while testing. BrowserStack’s real device cloud allows you to run tests on 3,500+ real devices and browsers, supporting all major test automation frameworks and CI/CD tools for a seamless experience. Moreover, you can execute multiple tests simultaneously with parallel testing, accelerating your test cycles.

Try BrowserStack Now

Tags
Automation Testing Manual Testing Real Device Cloud Website Testing