In the previous article we learnt about the getByText()
and getByRole()
queries. The getByX()
queries throw an error and immediately cause tests to fail if they are not present in the DOM.
But often we need to test for elements that perhaps appear in the DOM after a certain action is performed such as api requests or another user interaction.
queryByX()
, findByX()
To do this we need to use two variant queries; queryByX()
and findByX()
.
// App.js
import { useState } from "react";
const App = () => {
const [text, setText] = useState("Hello Sauce!");
// Changes header text after interval of 500ms
const handleClick = () => {
setTimeout(() => {
setText("Goodbye Sauce!");
}, 500);
};
return (
<div>
<h1>{text}</h1>
<button onClick={handleClick}>click me</button>
</div>
);
};
export default App;
queryByX - returns null if the element is not found. This can then be validated with the toBeNull()
assertion.
Below we see the <h1>
text is set to ‘Hello Sauce!’. Since we queryByText()
for ‘Goodbye Sauce!’ it returns null and we can assert that the header text is null.
//App.test.js
import App from "./components/App";
import { render, screen } from "@testing-library/react";
test("Header should not show Goodbye yet", () => {
// Render App
render(<App />);
// Attempt to extract the header element
const header = screen.queryByText("Goodbye Sauce!");
// Assert null as we have not clicked the button
expect(header).toBeNull();
});
findByX - queries are used to query for asynchronous elements that may eventually appear in the DOM. This methods return a promise.
//App.test.js
import App from "./components/App";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
test("should show text content as Goodbye Sauce!", async () => {
// Render App
render(<App />);
// Extract button node
const button = screen.getByRole("button");
// click button
userEvent.click(button);
// Wait for the text 'Goodbye Sauce!' to appear
const header = await screen.findByText("Goodbye Sauce!");
// Assert header to exist in the DOM
expect(header).toBeInTheDocument();
});
The example above (App.js) has a setTimeout on the handle click meaning the ‘Goodbye Sauce!’ text does not appear immediately after the use clicks the button.
We must identify the unit test callback as async
and the findByText()
method is proceeded with the await
keyword.
The previous methods allowed us to test components that render asynchronously but to test components that disappear asynchronously there is another RTL function called waitFor()
.
The waitFor()
function also returns a promise so we have to preface it’s call with the await keyword.
// header.js
export const Header = () => {
const handleClick = () => {
setTimeout(() => {
document.querySelector("h1").remove();
}, 250);
};
return (
<div>
<h1>Hey Sauce!</h1>
<button onClick={handleClick}>Remove Header</button>
</div>
);
};
We combine the waitFor()
method with the queryByX()
methods.
// header.test.js
import { waitFor, render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";
import { Header } from "./header.js";
test("should remove header display", async () => {
// Render Header
render(<Header />);
// Extract button node
const button = screen.getByRole("button");
// click button
userEvent.click(button);
// Wait for the element to be removed asynchronously
await waitFor(() => {
const header = screen.queryByText("Hey Sauce!");
expect(header).toBeNull();
});
});
💻 Software engineer. React enthusiast. Elm and Elixir curious 💻