This episode covers real testing patterns: use cases for frontend apps, API clients, and library packages, behavior-driven testing patterns, data-driven tests with parameterized cases, and test organization best practices.

All the techniques you've learned now come together as real testing patterns. Episode 20 covers concrete use cases for three types of projects — frontend apps, API clients, and library packages — then explores behavior-driven testing patterns, data-driven tests with parameterized cases, and test organization best practices.
Good patterns are born from experience, and this episode collects the ones that have most often proven useful. After this, you won't just know Jest's API; you'll know when and how to use each piece.
For a frontend app, focus on behavior visible to users. The combination of React Testing Library and user-event gives a realistic picture:
test("form login menampilkan error saat kosong", async () => {
render(<FormLogin />);
await user.click(screen.getByRole("button", { name: "Masuk" }));
expect(screen.getByText("Email wajib diisi")).toBeInTheDocument();
});This test exercises a real interaction: the user clicks the button without filling the form, and the app responds with an error message. This pattern is more valuable than inspecting a component's internal details.
For an API client, mock the transport, then test the request-building and response-parsing logic:
test("client menambah header otentikasi", async () => {
fetch.mockResolvedValue({ ok: true, json: async () => ({ ok: 1 }) });
await client.getStatus();
expect(fetch).toHaveBeenCalledWith(
expect.stringContaining("/status"),
expect.objectContaining({ headers: expect.any(Object) }),
);
});expect.stringContaining("/status") and expect.objectContaining are asymmetric matchers — they check part of a value rather than the whole. They're perfect for validating that the URL and headers are correct without being tied to unimportant details.
For a library package, focus on the public API contract — what consumers of the library use:
test("formatRupiah memformat angka dengan benar", () => {
expect(formatRupiah(1250000)).toBe("Rp1.250.000");
expect(formatRupiah(0)).toBe("Rp0");
});Library tests emphasize stability: once an API is published, behavior changes can break consumers. Asserting exact output for various inputs is the clearest form of a contract.
The behavior-driven testing (BDD) pattern structures a test as a narrative flow: initial state, action, and expected result.
describe("keranjang belanja", () => {
test("menambahkan barang menaikkan total", () => {
// given
const keranjang = buatKeranjang();
// when
keranjang.tambah({ harga: 10000, qty: 2 });
// when
keranjang.tambah({ harga: 5000, qty: 1 });
// then
expect(keranjang.total()).toBe(25000);
});
});The given, when, and then comments make the test read like a story. This structure helps reviewers understand the flow without having to guess the intent of the assertions.
Instead of writing a separate test for each input, test.each generates many tests from a single data table:
test.each([
[10, 2, 5],
[9, 3, 3],
[7, 2, 3.5],
])("bagi %i dengan %i menghasilkan %i", (a, b, hasil) => {
expect(bagi(a, b)).toBeCloseTo(hasil);
});test.each([...]) accepts a data array, and each row becomes its own test with a name interpolated from the arguments. When one case fails, you immediately know which input is the problem.
Use test.each when there are many cases but the pattern is the same. Avoid it when each case needs significantly different setup — in that situation, separate explicit tests are clearer.
A few organizational rules keep the suite maintainable:
describe block per module or feature, with a clear name.src/
keranjang/
keranjang.js
keranjang.test.js
keranjang.helpers.jsThe structure above places tests next to the source code, with helpers separated into their own module. Consistent structure keeps the suite easy to navigate as the project grows.
Episode 20 brought everything together into real patterns: use cases for frontend apps, API clients, and library packages, the BDD given-when-then pattern, data-driven tests with test.each, and test organization best practices.
Key takeaways:
test.each generates many tests from a single data table.In the next episode, episode 21, we'll cover the ecosystem & tools — Testing Library, Cypress, Playwright, and ESLint, Jest community plugins and utilities, debugging tests in the IDE, and official learning resources.