Skip to content
Advertisement

How to use ReactTestUtils to Simulate onChange event to update State

I want to run unit tests on a simple score keeping component in React. My component has two State values, score and newPoints. An input element specifies the newPoints to add, and a button adds that to the score.

Score keeping component

Game.js:

import React, { useState } from "react";

function Game() {
  const [score, setScore] = useState(0);
  const [newPoints, setNewPoints] = useState(0);

  const AddPoints = (numPins) => {
    setScore(score + numPins);
  };

  const handlePointsChange = (event) => {
    console.log(event.target.valueAsNumber); // value received is 3
    setNewPoints(event.target.valueAsNumber);
  };

  const handleAdd = () => {
    console.log(newPoints); // newPoints is still 0
    AddPoints(newPoints);
  };

  return (
    <div>
      <h1>Game</h1>
      <p id="ScoreLabel">Score:{score}</p>
      <input
        id="PointsInput"
        type="number"
        onChange={handlePointsChange}
        value={newPoints}
      />
      <button id="AddButton" onClick={handleAdd}>
        Add
      </button>
    </div>
  );
}

export default Game;

Problem: The component works fine in my browser but I can’t seem to get the unit tests working.

The ReactTestUtils.Simulate function in the test script successfully calls my component’s handlePointsChange and the valueAsNumber received is correct. However, when my test script simulates a mouse click via dispatchEvent, the value of newPoints in handleAdd is still zero. It seems as though the value of newPoints state hasn’t been updated yet, even after calling setNewPoints

Game.test.js

import React from "react";
import ReactDOM from "react-dom";
import ReactTestUtils from "react-dom/test-utils"; // ES6
import { act } from "react-dom/test-utils";
import Game from "./Game";

let container;
let scoreLabel;
let pointsInput;
let addButton;

beforeEach(() => {
  container = document.createElement("div");
  document.body.appendChild(container);
  act(() => {
    ReactDOM.render(<Game />, container);
  });
  scoreLabel = container.querySelector("#ScoreLabel");
  pointsInput = container.querySelector("#PointsInput");
  addButton = container.querySelector("#AddButton");
});
afterEach(() => {
  document.body.removeChild(container);
  container = null;
  scoreLabel = null;
  addButton = null;
});

it("Add 3 points, Score should be 3", () => {
  act(() => {
    ReactTestUtils.Simulate.change(pointsInput, {
      target: { valueAsNumber: 3 },
    });
    addButton.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(scoreLabel.textContent).toBe("Score:3");
});

Console log:

FAIL  src/Game.test.js
  ● Console

    console.log
      3

      at handlePointsChange (src/Game.js:12:13)

    console.log
      0

      at handleAdd (src/Game.js:17:13)

  ● Add 3 points, Score should be 3

    expect(received).toBe(expected) // Object.is equality

    Expected: "Score:3"
    Received: "Score:0"

Could anyone point me in the right direction to get this unit test to work?

Advertisement

Answer

I managed to get the tests working by putting each event in separate act() blocks.

I guess this way it ensures that everything within the first act() block will finish rendering before executing the second act() block.

it("Add 3 points, Score should be 3", () => {
  act(() => {
    ReactTestUtils.Simulate.change(pointsInput, {
      target: { valueAsNumber: 3 },
    });
  });
  act(() => {
    addButton.dispatchEvent(new MouseEvent("click", { bubbles: true }));
  });
  expect(scoreLabel.textContent).toBe("Score:3");
});
User contributions licensed under: CC BY-SA
1 People found this is helpful
Advertisement