This episode teaches React component testing: integration with React Testing Library, rendering components and inspecting DOM output, testing events, props, state changes, and async UI, and combining snapshot testing for components.

Jest was born from the React ecosystem and remains the most natural partner for testing components. Episode 8 teaches you how to test UI components with a combination of Jest and React Testing Library — an approach focused on the behavior visible to users, not internal implementation details.
You'll learn to render components, inspect DOM output, test events and state changes, handle UI that loads data asynchronously, and combine snapshot testing for components. Everything runs in a jsdom environment without a real browser, keeping the suite fast and deterministic.
To test React, change testEnvironment to jsdom so the DOM object is available. Install the required packages:
npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-eventThe @testing-library/react package provides the render function, @testing-library/jest-dom adds DOM matchers like toBeInTheDocument, and @testing-library/user-event simulates user interactions realistically.
import "@testing-library/jest-dom";Just import it once in the setupFilesAfterEnv file so the matchers are available in all test files — we'll cover this configuration detail in episode 11.
The render function loads a component into the virtual DOM, then you inspect the result with queries like getByText and getByRole:
import { render, screen } from "@testing-library/react";
function Sapa({ nama }) {
return <h1>Halo, {nama}</h1>;
}
test("menampilkan sapaan", () => {
render(<Sapa nama="arif" />);
expect(screen.getByText("Halo, arif")).toBeInTheDocument();
});screen.getByText("Halo, arif") finds the element whose text matches exactly, and the toBeInTheDocument matcher from jest-dom confirms the element exists in the DOM. Prioritize queries that mirror how users find elements — getByRole and getByLabelText are better than class-based queries.
With user-event, you can click, type, and wait for the component to react:
import userEvent from "@testing-library/user-event";
import { render, screen } from "@testing-library/react";
import { useState } from "react";
function Penghitung() {
const [hitung, setHitung] = useState(0);
return (
<button onClick={() => setHitung((n) => n + 1)}>
Ditekan {hitung} kali
</button>
);
}
test("klik menaikkan penghitung", async () => {
const user = userEvent.setup();
render(<Penghitung />);
const tombol = screen.getByRole("button");
await user.click(tombol);
await user.click(tombol);
expect(tombol).toHaveTextContent("Ditekan 2 kali");
});userEvent.setup() creates an interaction instance, then user.click(tombol) simulates a real click including the mousedown and mouseup events. The toHaveTextContent assertion verifies that the rendered state really changed after two clicks.
Test different behaviors with different props. For example: a button is disabled when the disabled prop is true, or the text changes according to the prop. Every meaningful combination of props deserves its own test.
Components that load data from an API usually show a loading state first, then the result. To wait, use findBy, which returns a promise:
import { render, screen } from "@testing-library/react";
async function AmbilData() {
const data = await Promise.resolve({ pesan: "dimuat" });
return <div>{data.pesan}</div>;
}
test("menampilkan data setelah dimuat", async () => {
render(<AmbilData />);
expect(screen.getByText("dimuat")).toBeInTheDocument();
});For components that really do fetch, you'll mock fetch or the API module — a technique we'll explore in depth in episode 12. For now, remember that findByText waits internally until the element appears or times out, making it ideal for async UI.
Combine rendering and snapshots to monitor structural changes:
test("struktur kartu stabil", () => {
const { container } = render(<Kartu judul="Berita" isi="Isi berita" />);
expect(container).toMatchSnapshot();
});A React snapshot captures the entire rendered DOM. Use it selectively: snapshots for structures that rarely change, and explicit assertions for interactive behavior. Combining both gives comprehensive protection without making the suite rigid.
Episode 8 equipped you with behavior-centered UI component testing: React Testing Library integration, rendering and DOM queries, user interactions with user-event, async UI handling, and snapshots for component structure.
Key takeaways:
testEnvironment: "jsdom" to test React components.getByRole and getByText, mirroring how users see.user-event simulates real interactions; use setup() for a new instance.findBy waits for async elements to appear in the DOM.In the next episode, episode 9, we'll tackle TypeScript & Babel support — running Jest with TypeScript, configuring ts-jest or Babel, mapping source paths and module aliases, and properly testing typed code.