I'm using React Testing Library and Jest to test my simple React app.
I have a component MyTabs
that renders a navigation bar to switch tabs when the headings are clicked. So if tab1Heading
is clicked, it should show tab 1. If tab2Heading
is clicked, it should show tab 2, and so on. I'm using a simple switch/case to switch between three tabs, and the currently selected tab is stored in this.state
.
Here's the test I'm writing to test this:
1 it("switches tabs when heading is clicked", () => {
2 render(<MyTabs />);
3 const tab1Heading = screen.getByText("Tab 1");
4 const tab2Heading = screen.getByText("Tab 2");
5 const tab3Heading = screen.getByText("Tab 3");
6
7 fireEvent.click(tab2Heading);
8 const tab2 = screen.queryByTestId("tab_2_id");
9 fireEvent.click(tab3Heading);
10 const tab3 = screen.queryByTestId("tab_3_id");
11 fireEvent.click(tab1Heading);
12 const tab1 = screen.queryByTestId("tab_1_id");
13 // click second time to see if elements are present
14 // 1 is already loaded, so click 2, then 3, then again 1
15 fireEvent.click(tab2Heading);
16 screen.debug(); // this shows tab_2_id
17 expect(tab1).not.toBeInTheDocument();
18 expect(tab2).toBeInTheDocument(); // <--- element could not be found in the document
19 expect(tab3).not.toBeInTheDocument();
20 fireEvent.click(tab3Heading);
21 expect(tab1).not.toBeInTheDocument();
22 expect(tab2).not.toBeInTheDocument();
23 expect(tab3).toBeInTheDocument();
24 fireEvent.click(tab1Heading);
25 expect(tab1).toBeInTheDocument();
26 expect(tab2).not.toBeInTheDocument();
27 expect(tab3).not.toBeInTheDocument();
28 });
I'm fetching the headings first, then I'm clicking each of them in turn (1 is already loaded, so 2 is clicked, then 3, then 1 again) and getting references of the loaded pages using queryByTestId
(since they can be null), then clicking the headings again and asserting which tabs should be loaded and which not.
Now the issue is that on line 18, it's failing. I have tried looking at the rendered output using screen.debug()
(line 16) and it does show the id that I'm trying to assert.
I'm lost here. If screen.debug()
shows the id, then why is the element not found in the document? Is it a reference issue from destroying and recreating the tabs due to the tab switching? That is, is the queryByTestId()
returned reference stale now?
I also tried using waitFor()
in the assertions, but that didn't do anything.
CodePudding user response:
You must wait to capture the element reference until you've triggered the app code that causes the desired elements to exist.
The RTL query methods look at the document as it is when they are executed -- they are not "live" pointers that automatically update if the document is changed after they get defined.
Change line 18 to this:
expect(screen.queryByTestId("tab_2_id")).toBeInTheDocument()
This also explains why your debugging trick is failing: you're changing the environment between the moment when you define tab2
and when you print the document. This is a case of bad experimental design, and it's something you can see just by looking at the code:
8 const tab2 = screen.queryByTestId("tab_2_id");
/*
All of the below lines could make the world of line 8 different from
the world of line 16:
*/
9 fireEvent.click(tab3Heading);
..
11 fireEvent.click(tab1Heading);
..
15 fireEvent.click(tab2Heading);
// all the above lines could change the world
16 screen.debug(); // this shows tab_2_id