React testing library how to use waitFor
Asked Answered
F

3

50

I'm following a tutorial on React testing. The tutorial has a simple component like this, to show how to test asynchronous actions:

import React from 'react'

const TestAsync = () => {
  const [counter, setCounter] = React.useState(0)

  const delayCount = () => (
    setTimeout(() => {
      setCounter(counter + 1)
    }, 500)
  )
  
  return (
    <>
      <h1 data-testid="counter">{ counter }</h1>
      <button data-testid="button-up" onClick={delayCount}> Up</button>
      <button data-testid="button-down" onClick={() => setCounter(counter - 1)}>Down</button>
    </>
  )
}
  
export default TestAsync

And the test file is like this:


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

afterEach(cleanup);
  
it('increments counter after 0.5s', async () => {
  const { getByTestId, getByText } = render(<TestAsync />); 

  fireEvent.click(getByTestId('button-up'))

  const counter = await waitForElement(() => getByText('1')) 

  expect(counter).toHaveTextContent('1')
});

The terminal says waitForElement has been deprecated and to use waitFor instead.

How can I use waitFor in this test file?

Fransen answered 14/1, 2021 at 19:20 Comment(0)
Z
51

If you're waiting for appearance, you can use it like this:

it('increments counter after 0.5s', async() => {
  const { getByTestId, getByText } = render(<TestAsync />);

  fireEvent.click(getByTestId('button-up'));
  
  await waitFor(() => {
    expect(getByText('1')).toBeInTheDocument();
  });
});

Checking .toHaveTextContent('1') is a bit "weird" when you use getByText('1') to grab that element, so I replaced it with .toBeInTheDocument().

Zachar answered 14/1, 2021 at 19:33 Comment(2)
Would it be also possible to wrap the assertion using the act function? Based on the docs I don't understand in which case to use act and in which case to use waitFor.Amyl
@RobBauer act is used when you want to simulate user interaction, waitFor is intended for async operations and DOM changesMistassini
A
6

Current best practice would be to use findByText in that case. This function is a wrapper around act, and will query for the specified element until some timeout is reached.

In your case, you can use it like this:

it('increments counter after 0.5s', async () => {
  const { findByTestId, findByText } = render(<TestAsync />); 

  fireEvent.click(await findByTestId('button-up'))

  const counter = await findByText('1')
});

You don't need to call expect on its value, if the element doesn't exist it will throw an exception

You can find more differences about the types of queries here

Angell answered 23/8, 2022 at 2:43 Comment(0)
A
3

Would it be also possible to wrap the assertion using the act function? Based on the docs I don't understand in which case to use act and in which case to use waitFor.

The answer is yes. You could write this instead using act():

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

        it('increments counter after 0.5s', async() => {
          const { getByTestId, getByText } = render(<TestAsync />);

// you wanna use act() when there is a render to happen in 
// the DOM and some change will take place:
          act(() => {
              fireEvent.click(getByTestId('button-up'));
          });
            expect(getByText('1')).toBeInTheDocument();
        });

Hope this helps.

Aroid answered 8/6, 2022 at 15:43 Comment(1)
I think this is wrong, fireEvent should already use act internallyLabour

© 2022 - 2024 — McMap. All rights reserved.