Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React Testing Library's waitFor not working

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.

like image 487
darKnight Avatar asked Mar 16 '21 18:03

darKnight


People also ask

How do you use waitFor in react test library?

In order to use this function, we need to import it from @testing-library/react . import { waitFor } from '@testing-library/react'; As with the other async functions, the waitFor() function returns a Promise, so we have to preface its call with the await keyword.

What is a testing library?

The Testing Library family of libraries is a very light-weight solution for testing without all the implementation details. The main utilities it provides involve querying for nodes similarly to how users would find them. In this way, testing-library helps ensure your tests give you confidence in your UI code.


Video Answer


3 Answers

I found the answer here: React Testing Library - using 'await wait()' after fireEvent

TLDR: "You can not use wait with getBy*. getBy is not async and will not wait." Better is to use findBy*. This is the async version of getBy.

like image 93
mthomas Avatar answered Oct 08 '22 08:10

mthomas


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.

like image 41
Rajiv Punjabi Avatar answered Oct 08 '22 06:10

Rajiv Punjabi


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

like image 6
Brandon G Avatar answered Oct 08 '22 06:10

Brandon G