42

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?

AshNaz87
  • 376
  • 3
  • 14
lomine
  • 873
  • 5
  • 20
  • 36

3 Answers3

44

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().

Zsolt Meszaros
  • 21,961
  • 19
  • 54
  • 57
  • 7
    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`. – Rob Bauer Mar 26 '21 at 06:24
4

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

Feder 240516
  • 61
  • 1
  • 2
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.

DToxVanilla
  • 214
  • 2
  • 8