testing-library / react-testing-library

🐐 Simple and complete React DOM testing utilities that encourage good testing practices.

Home Page:https://testing-library.com/react

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

findBy* query fails but waitFor passes

hyalen opened this issue · comments

  • react version: 18.2.0
  • @testing-library/react version: 14.0.0
  • @testing-library/user-event version: 14.4.3
  • @testing-library/jest-dom version: 5.16.5
  • @chakra-ui/react version: 2.2.1
  • Testing Framework and version: Jest 27.5.1
  • DOM Environment: jest-environment-jsdom / Google Chrome

Relevant code or config:

Component:

import { Box, Collapse, Text } from "@chakra-ui/react";
import { useState } from "react";

export default function Component() {
  const [isOpen, setIsOpen] = useState(false);

  return (
    <Box>
      <button onClick={() => setIsOpen((isOpen) => !isOpen)}>
        What's the meaning of life?
      </button>
      <Collapse startingHeight={0} in={isOpen}>
        <Text marginTop="1rem">The answer is 42</Text>
      </Collapse>
    </Box>
  );
}

Test:

import Component from "@components/Component";
import userEvent from "@testing-library/user-event";
import { screen, render, waitFor } from "@testing-library/react";

const setup = (ui: React.ReactNode) => {
  return {
    user: userEvent.setup(),
    ...render(ui),
  };
};

describe("Collapse component test", () => {
  test("this is not working properly with findBy*", async () => {
    const { user } = setup(<Component />);

    await user.click(
      screen.getByRole("button", { name: "What's the meaning of life?" })
    );
    expect(await screen.findByText("The answer is 42")).toBeVisible();
  });
  test("this is working as expected with waitFor + getBy*", async () => {
    const { user } = setup(<Component />);

    await user.click(
      screen.getByRole("button", { name: "What's the meaning of life?" })
    );

    await waitFor(() =>
      expect(screen.getByText("The answer is 42")).toBeVisible()
    );
  });
});

What you did:

I'm trying to validate a collapse component that expands/shrinks the visibility of the content. It has an animation for both the open and close actions, so I need to wait for the element to be visible on the screen.

What happened:

I tried using both findBy* and waitFor + getBy*, but they have different results, even though that they're considered equivalent. I even tried to increase the timeout for the 1st test I mentioned above, but the result remains the same.

Reproduction:

  • Create a project with the same dependencies as described above
  • copy and paste all the code I used
  • assert the results

Problem description:

This behavior is a problem because the findBy* usage should have a consistent and deterministic result, compared to the waitFor + getBy* one. This issue leads to a false-negative type of test, confusing devs on whether the error is coming from the project's side, or RTL itself.

Suggested solution:

I haven't dig in too much in RTL's codebase, so I'm unsure on what the solution should be.

Hi @hyalen, thanks for opening this.
It might be confusing, but the two assertions you're presenting are not equivalent.

expect(await screen.findByText("The answer is 42")).toBeVisible();

This assertion awaits to get an element (using findBy), once the element is in the DOM, it resolves and the assertion is made.

await waitFor(() =>
  expect(screen.getByText("The answer is 42")).toBeVisible()
);

This assertion waits for the expect to be truthy until the timeout passes. There's a slight difference between the two.
This is the desired behavior so I'm resolving this. If you have any further questions or you think I'm wrong here, feel free to comment & re-open.
Thanks!

For those who might want a more detailed explanation: https://codsen.com/articles/waitfor-getby-vs-findby