Arrange-Act-Assert organizes a test into setup, behavior execution, and result verification. The ACT phase may be omitted when no behavior occurs, and a test may contain multiple ACT–ASSERT rounds. section 10.2.1
Use the pattern to make component tests easier to read and maintain:
- Arrange by rendering the component and preparing the interaction environment. section 10.2.1
- Act by querying an accessible control and performing a user interaction. section 10.2.1
- Assert by querying the resulting interface and checking the expected state. section 10.2.1
- Extract repeated rendering, queries, user setup, and rerendering into a custom setup function when the test file grows. section 10.2.1
Testing a click counter
Arrange with render(<Counter />), create a userEvent instance, and locate the Increment button. Act with await user.click(increment). Assert that a heading named “Counter: 1” is present. For prop changes, arrange with render(<Counter start={10} />), act with rerender(<Counter start={20} />), and assert the retained displayed value. section 10.2.1
Listing 10.5 Clicking a button
import { render, screen } from "@testing-library/react";
import userEvent
from "@testing-library/user-event"; #1
import "@testing-library/jest-dom";
import { Counter } from "./Counter";
test( #2
"Counter should start at the given value", #2
() => {...}, #2
); #2
test( #2
"Counter should start at 0 if no value is given", #2
() => {...}, #2
); #2
test( #3
"Counter should increment when button is clicked", #3
async () => { #3
// ARRANGE
render(<Counter />);
// ACT
const user = userEvent.setup(); #4
const increment = screen.getByRole( #5
"button", #5
{ name: "Increment" }, #5
); #5
await user.click(increment); #6
// ASSERT
const heading = screen.getByRole( #7
"heading", #7
{ name: "Counter: 1" }, #7
); #7
expect(heading).toBeInTheDocument();
});
Shows the complete Arrange-Act-Assert flow for an asynchronous button interaction. section 10.2.1
Listing 10.7 The full test file with a convenient setup function
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import { Counter } from "./Counter";
function setup(start) { #1
const { rerender } = render(<Counter start={start} />);
const getCounter = (val) => screen.getByRole( #2
"heading", #2
{ name: `Counter: ${val}` }, #2
); #2
const increment = screen.getByRole("button", { name: "Increment" });
const decrement = screen.getByRole("button", { name: "Decrement" });
const user = userEvent.setup();
return { #3
getCounter,
increment,
decrement,
user,
rerender: (newStart) => rerender(<Counter start={newStart} />),
};
}
test("Counter should start at the given value", () => {
// ARRANGE
const { getCounter } = setup(10); #4
// ASSERT
expect(getCounter(10)).toBeInTheDocument(); #5
});
test("Counter should start at 0 if no value is given", () => {
// ARRANGE
const { getCounter } = setup();
// ASSERT
expect(getCounter(0)).toBeInTheDocument();
});
test("Counter should increment when button is pressed", async () => {
// ARRANGE
const { getCounter, increment, user } = setup();
// ACT
await user.click(increment); #6
// ASSERT
expect(getCounter(1)).toBeInTheDocument();
});
test("Counter should decrement when button is clicked", async () => {
// ARRANGE
const { getCounter, decrement, user } = setup();
// ACT
await user.click(decrement);
// ASSERT
expect(getCounter(-1)).toBeInTheDocument();
});
test("Counter should not update value if passed start property changes", () => {
// ARRANGE
const { getCounter, rerender } = setup(10);
// ACT
rerender(20); #7
// ASSERT
expect(getCounter(10)).toBeInTheDocument();
});
Shows how a shared setup function centralizes queries, user-event initialization, and rerender support. section 10.2.1





