Complete Guide to Solving "update was not wrapped in act()" Warning in React Testing

Nov 25, 2025 · Programming · 10 views · 7.8

Keywords: React Testing | act Warning | Asynchronous Components | Testing Library | Jest

Abstract: This article provides a comprehensive analysis of the common "update was not wrapped in act()" warning in React component testing. Through a complete test case of a data-fetching component, it explains how to properly handle asynchronous state updates using waitForElement and findBy* selectors, ensuring test coverage of all React lifecycles. The article compares different testing approaches and provides best practices with code examples.

Problem Background and Warning Analysis

In React component testing, when components contain asynchronous operations (such as API calls), the "Warning: An update to ComponentName inside a test was not wrapped in act(...)" warning frequently appears. This warning indicates that React detected state updates occurring after the test completed, which may lead to unreliable test results.

Taking the Hello component from the Q&A data as an example, this component initiates an asynchronous data request via useEffect after mounting:

export default function Hello() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const response = await axios.get('https://jsonplaceholder.typicode.com/posts');
      setPosts(response.data);
    };

    fetchData();
  }, []);

  return (
    <div>
      <ul data-testid="list">
        {posts.map((post) => (
          <li key={post.id}>{post.title}</li>
        ))}
      </ul>
    </div>
  );
}

The component updates state via setPosts after data retrieval completes, triggering a re-render. If the test finishes before the state update occurs, the act warning is generated.

Issues with Initial Testing Approach

The original test code attempted to handle asynchronous rendering using waitForElement:

it('renders hello correctly', async () => {
  mockAxios.get.mockResolvedValue({
    data: [
        { id: 1, title: 'post one' },
        { id: 2, title: 'post two' },
      ],
  });

  const { asFragment } = await waitForElement(() => render(<Hello />));

  expect(asFragment()).toMatchSnapshot();
});

This approach has several issues: the usage of waitForElement is imprecise, the test doesn't explicitly wait for the data loading completion state, and the render call method may not properly handle asynchronous updates.

Recommended Solutions

Method 1: Using findBy* Selectors (Recommended)

React Testing Library provides findBy* series selectors that return Promises and naturally handle asynchronous operations:

it('renders hello correctly', async () => {
  axios.get.mockResolvedValue({
    data: [
      { id: 1, title: 'post one' },
      { id: 2, title: 'post two' },
    ],
  });
  
  const { findByTestId, asFragment } = render(<Hello />);

  // Wait for list element to appear
  const listNode = await findByTestId('list');
  
  // Verify rendering results
  expect(listNode.children).toHaveLength(2);
  expect(asFragment()).toMatchSnapshot();
});

This method is more concise and intuitive. findByTestId waits for the specified element to appear in the DOM, ensuring asynchronous operations complete before assertions.

Method 2: Using waitForElement (Legacy Approach)

For more granular control, waitForElement can be used:

it('renders hello correctly', async () => {
  axios.get.mockResolvedValue({
    data: [
      { id: 1, title: 'post one' },
      { id: 2, title: 'post two' },
    ],
  });
  
  const { getByTestId, asFragment } = render(<Hello />);

  const listNode = await waitForElement(() => getByTestId('list'));
  expect(listNode.children).toHaveLength(2);
  expect(asFragment()).toMatchSnapshot();
});

Deep Understanding of act() Function

React's act() function ensures all state updates and side effects complete before assertions. When testing involves:

relevant operations must be wrapped in act(). React Testing Library's asynchronous query methods (like findBy*, waitFor, etc.) internally use act(), so manual calls are unnecessary.

Key Testing Configuration Points

Complete test configuration should include:

import React from 'react';
import { render, cleanup, waitForElement } from '@testing-library/react';
import axios from 'axios';
import Hello from '.';

// Mock axios
jest.mock('axios');

// Clean up test environment
afterEach(cleanup);

Key configuration explanations:

Best Practices Summary

Based on analysis of Q&A data and reference articles, the following best practices are recommended:

  1. Prefer findBy* selectors: More concise code with clearer semantics
  2. Add data-test attributes: Such as data-testid for easier element targeting
  3. Comprehensively verify rendering results: Including element counts, content, and snapshots
  4. Properly handle asynchronous operations: Ensure tests wait for all state updates to complete
  5. Maintain test isolation: Use cleanup to prevent test interference

By following these practices, reliable, warning-free React component tests can be written, ensuring correct application behavior across various scenarios.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.