How to test async useEffect?

Viewed 4526

How to test async useEffect with react-testing-libs ?

i have:

// in other file
const getData = () => {
  return new Promsise(resolve => setTimeout(() => resolve([1, 2, 3]), 5000));
};

const MyComponent = () => {
  const [data, setData] = useState([]);

  useEffect(() => {
    const fetchData = async () => {
      const data = await getData();
      setData(data);
    };
    fetchData();
  }, []);

  return <div>{data.map(item => <span>{item}</span>)}</div>
};

How test this component?

my test now:

it('MyComponent test', async () => {
  await act( async () => {
    render(<MyComponent/>, container);
  });

  expect(container.innerHTML).toMatchSnapshot();
});

But act, render doesn't wait for useEffect().

Why ?

2 Answers

react-testing-library has a clean solution for this. You can wait for the element to appear in the DOM, which means your async useEffect ran its course.

It's called waitFor and it will wait for an assertion to complete before continuing the code. You can use the other utilities like getByText to expect

it('MyComponent test', async () => {
  await act( async () => {
    render(<MyComponent/>, container);
  });

  await waitFor(() => {
    // I'd look for a real text here that is renderer when the data loads
    expect(container.queryElement('span')).toBeDefined();
  })

  expect(container.innerHTML).toMatchSnapshot();

});

Please try the following:

test('My Test', async () => {
  const queries = render(<MyComponent/>, container);

  expect(await queries.findByText((content, element) => element.tagName.toLowerCase() === 'span')).toBeInTheDocument();

  expect(container.innerHTML).toMatchSnapshot();
})

I couldn't test it, so please let me know if it works.

I read this comment on a react-testing-library issue and then I took a look into this piece of documentation. It seems that there are many ways of customizing this test behavior.

Related