React Testing Library's waitFor not working

Viewed 26256

I am using React Testing Library to unit test my ReactJS code. There are several async events in the UI, like fetching data and displaying a new page on click of button. The React code is somewhat like this:

// Inside ParentComponent.tsx
const [isChildVisible, setChildVisibility] = useState(false);
const showChild = () => setChildVisibility(true);

return(
  <>
      <button data-testid="show-child" onClick={showChild}>Show Child</button>
      {isChildVisible && <ChildComponent {..childProps}/>}
 </>
)

Where ChildComponent mounts, it fetches some data and then re-renders itself with the hydrated data. My unit test looks like:

jest.mock('../../../src/service'); // mock the fetch functions used by ChildComponent to fetch its data

describe('ParentComponent', () => {
    test('renders ChildComponent on button click', async () => {
        const screen = render(<ParentComponent />);
        userEvent.click(screen.getByTestId('show-child'));
        await (waitFor(() => screen.getByText('text rendered by child')));
    });
});

When I run this test, I get the error "TestingLibraryElementError: Unable to find an element with the text: text rendered by child. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.".

I am not sure why it's happening, but one of the reason maybe that it's taking more than one second to hydrate and render the child component. Thus I want to change the default wait time for waitFor, but I can't find a way to do it from the docs (the default wait time is one second). So is it possible to change the default wait time?

EDIT: Increasing the wait time is still causing the same error. So the issue is something else.

4 Answers

It's specified within the documentation. waitFor Documentation

function waitFor<T>(
  callback: () => T | Promise<T>,
  options?: {
     container?: HTMLElement
     timeout?: number //This is 1000ms. Change timeout here.
     interval?: number
     onTimeout?: (error: Error) => Error
     mutationObserverOptions?: MutationObserverInit
  }
): Promise<T>

//For 3 seconds.
await (waitFor(() => screen.getByText('text rendered by child'),{timeout:3000}));

The default timeout is 1000ms which will keep you under Jest's default timeout of 5000ms.

I had an issue similar to this when I was setting up testing for a test application. The way I fixed this issue was to force re-render the component.

In this case your code would look something like:

import {render, screen} from "@testing-library/react";

describe('ParentComponent', () => {
  test('renders ChildComponent on button click', async () => {

    const {rerender} = render(<ParentComponent />);
    userEvent.click(screen.getByTestId('show-child'));

    rerender(<ParentComponent />)
    await (waitFor(() => screen.getByText('text rendered by child')));
  });
});

I hope this works for you. Also to be noted that you can use the screen export from the react testing library. It seems like there should be a way to do this automatically, but I haven't been able to find it.

Adding link to the rerender docs: https://testing-library.com/docs/react-testing-library/api/#rerender

For those who are using jest-expo preset which breaks this functionality you need to modify the jest-expo preset to include the code from testing-library/react-native

/* eslint-disable @typescript-eslint/no-var-requires */
const { mergeDeepRight } = require("ramda");
const jestExpoPreset = require("jest-expo/jest-preset");
const testingLibraryPreset = require("@testing-library/react-native/jest-preset");

/*
 * Modify the existing jest preset to implement the fix of @testing-library/react-native to get the
 * async waitFor working with modern timers.
 */
jestExpoPreset.setupFiles = [
  testingLibraryPreset.setupFiles[0],
  ...jestExpoPreset.setupFiles,
  testingLibraryPreset.setupFiles[testingLibraryPreset.setupFiles.length - 1],
];
module.exports = mergeDeepRight(jestExpoPreset, {
  testResultsProcessor: "jest-sonar-reporter",
  moduleFileExtensions: ["js", "jsx", "ts", "tsx", "yml"],
  modulePathIgnorePatterns: ["<rootDir>/lib/"],
  globals: {
    "ts-jest": {
      babelConfig: "./babel.config.js",
    },
  },
});
Related