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.
Advertisement
Answer
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(); });