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
.
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"); });