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