Table Of Contents
1. Introduction
2. Examples and Methods
3. Conclusion
Hi everyone. It is another week and another post from yours truly. This post will be a continuation from the "Implementing Jest and RTL for beginners" series.
Introduction
For this post, we will cover a few more testing methods with the TicTacToe game I created with React framework just to familiarize with Jest and RTL. More details of this game can be found in the previous post of this series.
Examples and Methods
In the previous post, we looked at setting up Jest and RTL in a React Application, went thorough some definitions and did a basic render test to confirm the game board is render on the screen. For today, we will cover two more types of test we can do for the game.
Test: Click Action
A click test is simulating a click event to test if the logic works by confirming what is rendered on the screen. This means the only way to confirm the click is working as expected is through confirmation on what is "expected" to render on the screen when it is clicked. For example in the Board
component, nine Square
components were components were rendered as follows:
const [arr, setArr] = useState<any[]>(
Array(9)
.fill("")
.map((a) => a),
);
const [select, setSelect] = useState<String>("X"); // start with X
const [winner, setWinner] = useState<String>("");
const [disable, setDisable] = useState<boolean>(false);
const onHandleSelect = (i: any) => {
if (arr[i] === "X" || arr[i] === "O") return;
if (select === "X") {
setSelect("O");
arr[i] = "X";
} else {
setSelect("X");
arr[i] = "O";
}
const result = decideIfThereIsWinner(arr, select);
if (result === true) {
setWinner(select);
setDisable(true);
}
if (drawCombi.find((a) => arr.toString() === a.toString())) {
setWinner("Draw");
setDisable(true);
}
};
return (
<div className={classes.container} data-testid="board">
<span data-testid="playerTurn">Player {select} Turn</span>
<div className={classes.gridContainer}>
{arr.map((a, i) => (
<Square
key={Math.random()}
index={i}
onHandleSelect={onHandleSelect}
moveName={a}
disable={disable}
/>
))}
</div>
{winner ? (
<h2 data-testid="winnerMessage">
{winner === "Draw"
? "Round Draw. Restart Round."
: `Player ${winner} is the Winner!`}
</h2>
) : (
""
)}
<button
onClick={onHandleReset}
type="button"
className={classes.buttonReset}
>
reset
</button>
</div>
)
and each Square
component is a button as shown:
import React from "react";
import classes from "./Square.module.scss";
interface SelectProps {
onHandleSelect: (event: React.MouseEvent<HTMLButtonElement>) => void;
moveName: String;
index: any;
disable: boolean;
}
function Square({ moveName, onHandleSelect, index, disable }: SelectProps) {
return (
<div className={classes.container} data-testid="board">
<button
data-testid={`squareButton${index}`}
onClick={() => {
onHandleSelect(index);
}}
key={Math.random()}
type="button"
disabled={disable}
className={classes.square}
>
{moveName}
</button>
);
}
export default Square;
For this test, we are going to test when we click the square button, will an "X" show up in the Square
box. Dirst we have to confirm that X is the current turn. For that, we will run this test:
describe(Board, () => {
test("First player turn is X", () => {
const { getByTestId } = render(<Board />);
const playerTurnMsg = getByTestId("playerTurn");
expect(playerTurnMsg).toHaveTextContent("Player X Turn");
});
}
which means we will find the HTML element which has a data_testid of "playerTurn" (line 2 in the JSX portion of the board
component) and we expect the "player turn message" rendered on the screen to be "Player X turn". Once we confirm this, we will do a click test on the square itself as follows:
test("Next player turn is O after click", () => {
const { getByTestId } = render(<Board />);
const button = getByTestId("squareButton0");
const playerTurnMsg = getByTestId("playerTurn");
fireEvent.click(button);
expect(playerTurnMsg).toHaveTextContent("Player O Turn");
expect(getByTestId("squareButton0")).toHaveTextContent("X"); // confirm click produce X on button
});
In this test, we use a new method from React Testing Library called fireEvent
. This method acts like a click action. Also in this test we have two expected result to happen
- The player turn message (data_testid=
playerTurn
) to change to "Player O Turn". - The square that we click to have a string "X". In this case we will use the
.toHaveTextContent
matcher from Jest to confirm that. Note that I selected the data_testid ofsquareButton0
for the first square in the board. If the test is shown in the board, this is how it will render after the click event happens.
Because the component rendered as expected, the test was successful.
Test: Test number of components rendered
The next test is to check the number of Square
components rendered. On the board, we expect nine squares to be rendered to form the game board. We can do a basic test as follows:
test("Game Rendered nine squares", () => {
const { container } = render(<Board />);
const renderSquares = container.getElementsByClassName("square");
expect(renderSquares).toHaveLength(9);
});
What is different about this test is, since we have specific data-testid for each Square
component (squareButton0, squareButton1, etc), we cannot use the data-testid to find out the number of Square
components being rendered. We need to find a common identifier in the Square
component which Jest and RTL is able to search and count the number of component.
Hence we will not be using the query getByTestId
and instead use the query getElementsByClassName
. However before that, we must destructure the container
object from the render
method. This is because the container
object consist of DOM node of your rendered React Element. These methods enable us to search for specific attributes in the DOM (eg getAttribute
, getElementByTagName
, etc). Then, we can use the method to call on the className (which is square
) for the Square
component button
. From there, we will call a matcher toHaveLength
and expect
that the number of renderSquares to be 9. If everything goes well, the test should be successful.
Just a note: According to RTL official docs, it is not advisable to use container
frequently to query for rendered elements in our app as it maybe not be resilient to change as compared to other built in queries in RTL such as getByTestId
. But for the sake for demonstration, we will use this. If we want to use RTL built in queries, we can add in placeholders
into the Square
component buttons and use getByPlaceHolderText
query to find the html element.
Conclusion
Well, this conclude the second part of this series. In the last part, we will dive into how do we test the winning combination of the game and how to test for conditional conditions (such as the game win or draw scenario) that renders specific content/elements.
Thank you!
Top comments (0)