DEV Community

Cover image for Implementing Jest and RTL for beginners (2/3)
Jareth Tan
Jareth Tan

Posted on

Implementing Jest and RTL for beginners (2/3)

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>
)
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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");
  });
}
Enter fullscreen mode Exit fullscreen mode

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
  });
Enter fullscreen mode Exit fullscreen mode

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

  1. The player turn message (data_testid= playerTurn) to change to "Player O Turn".
  2. 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 of squareButton0 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.

This is what the test is trying to simulate when the click event happens in the first square of the board

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);
  });
Enter fullscreen mode Exit fullscreen mode

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)