cjl750
cjl750

Reputation: 4629

Preventing "not wrapped in act(...)" Jest warning when state update doesn't affect UI

I'm trying to figure out if there is a way to prevent the "not wrapped in act(...)" warning thrown by Jest/testing-library when I have nothing to assert after the state update that causes the warning happens, or if I should just ignore this warning.

Suppose I have this simple component:

import React, {useEffect, useState} from 'react';
import {getData} from 'services';

const MyComponent = () => {
  const [arr, setArr] = useState([]);

  useEffect(() => {
    (async () => {
      const {items} = await getData();
      setArr(items);
    })();
  }, []);

  return (
    <div>
      {!(arr.length > 0) && <p>no array items</p>}
      {arr.length > 0 && (
        <ul>
          {arr.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ul>
      )}
    </div>
  );
};

export default MyComponent;

Suppose I want to simply test that this component renders okay even if getData() doesn't return any data for me.

So I have a test like this:

import React from 'react';
import {getData} from 'services';
import {render, screen} from 'testUtils';
import MyComponent from './MyComponent';

jest.mock('services', () => ({
  getData: jest.fn(),
}));

it('renders', () => {
  getData.mockResolvedValue({items: []});

  render(<MyComponent />);

  expect(screen.getByText('no array items')).toBeInTheDocument();
});

This test will pass, but I'll get the "not wrapped in act(...)" warning because the test will finish before getData() has a chance to finish.

In this case, the response from getData() sets arr to the same value (an empty array) as I have initially set it to at the top of the component. As such, my UI doesn't change after the async function completes—I'm still just looking at a paragraph that says "no array items"—so I don't really have anything I can assert that would wait for the state update to complete.

I can expect(getData).toHaveBeenCalledTimes(1), but that doesn't wait for the state to actually be updated after the function call.

I have attempted an arbitrary pause in the test to allow time for setArr(items) to happen:

it('renders', async () => {
  getData.mockResolvedValue({items: []});

  render(<MyComponent />);

  expect(screen.getByText('no array items')).toBeInTheDocument();
  
  await new Promise(resolve => setTimeout(resolve, 2000));

  expect(screen.getByText('no array items')).toBeInTheDocument();
});

But that doesn't seem to help, and I'm honestly not sure why.

Is there a way to handle this situation by modifying only the test?

I am sure I could fix the problem by refactoring MyComponent, e.g., by passing arr to MyComponent as a prop and moving the getData() call to a parent component, or creating some custom prop used only for testing that would skip the getData() call altogether, but I don't want to be modifying components purely to avoid warnings in tests.

I am using testing-library/react, v11.2.2.

Upvotes: 3

Views: 3354

Answers (2)

Akaisteph7
Akaisteph7

Reputation: 6476

@juliomalves's answer is spot on.
However, I had to put this await in my beforeEach:

import {render, fireEvent} from '@testing-library/react';
import MyComponent from './MyComponent';

...

describe('MyComponent should', () => {
  let getByText, getByTestId, getAllByTestId, getByLabelText;
  beforeEach(async () => {
    let findByText;
    ({
      getByText,
      getByTestId,
      getAllByTestId,
      getByLabelText,
      findByText,
    } = render(<MyComponent {...props} />));

    // Have this here to avoid warnings because of setting state variables
    await findByText('no array items');
  })

  ...
});

Upvotes: 0

juliomalves
juliomalves

Reputation: 50258

You can use findByText (a combination of getByText and waitFor) to ensure all updates have happened when the assertion resolves.

it('renders', async () => {
    getData.mockResolvedValue({items: []});
    render(<MyComponent />);
    expect(await screen.findByText('no array items')).toBeInTheDocument();
});

Upvotes: 2

Related Questions