Lompat ke konten
Kembali ke modul

Testing, Debugging, dan Kualitas Kode · 3/7

Menguji component React

Cari berdasarkan role dan label, bukan nama class. Uji perilaku, bukan implementasi.

Baca 30 menit

Setelah pelajaran ini kamu bisa

  • Mencari elemen dengan cara pengguna menemukannya
  • Menguji perilaku alih-alih implementasi
  • Menjelaskan kenapa `getByRole` adalah query yang diutamakan
  • Menyebutkan apa yang tidak bisa diuji sebagai unit test di React

Aturan untuk test component, dari prinsip panduan Testing Library sendiri: temukan sesuatu dengan cara pengguna menemukannya. Pengguna tidak tahu nama class-mu. Mereka melihat sebuah button berlabel "Sign in".

tsx
import { render, screen } from "@testing-library/react";
import { expect, it } from "vitest";
import { Badge } from "./badge";

it("shows its label", () => {
  render(<Badge tone="success">Paid</Badge>);
  expect(screen.getByText("Paid")).toBeInTheDocument();
});

it("announces an icon-only button", () => {
  render(<CloseButton />);
  // The accessible name, not the markup.
  expect(screen.getByRole("button", { name: "Close dialog" })).toBeInTheDocument();
});
QueryDipakai saatPrioritas
getByRole(role, { name })Hampir selalu — button, link, heading, inputPilihan pertama
getByLabelTextField form yang punya labelBagus
getByTextTeks non-interaktifBoleh
getByTestIdTidak ada cara lain yang berhasilPilihan terakhir
container.querySelectorJangan pernah, di dalam testHindari
Coba sendiri

Query berdasarkan accessible name, diimplementasikan di atas pohon elemen tiruan. Perhatikan elemen mana yang bisa dan tidak bisa ditemukannya.

Hasil

Tekan Jalankan untuk melihat hasilnya.

Ini jalan di browser kamu, di dalam sandbox. Apa pun yang kamu tulis di sini tidak bisa merusak situs.

Perilaku, bukan implementasi

tsx
// ❌ Coupled to how it works. Renaming state breaks a passing test.
it("sets isOpen to true", () => {
  const { result } = renderHook(() => useDisclosure());
  act(() => result.current.open());
  expect(result.current.isOpen).toBe(true);
});

// ✅ Coupled to what the user sees. Survives any refactor that keeps behaviour.
it("shows the menu after clicking the trigger", async () => {
  render(<NavMenu />);
  expect(screen.queryByRole("menu")).not.toBeInTheDocument();

  await userEvent.click(screen.getByRole("button", { name: "Open menu" }));

  expect(screen.getByRole("menu")).toBeInTheDocument();
});
Test kedua akan tetap lolos meski component-nya ditulis ulang dari useState jadi useReducer, atau library menu-nya diganti sepenuhnya. Itulah intinya.

Tugas praktik

Tulis test untuk component Callout di proyek ini: render satu dengan sebuah judul dan children, lalu pastikan judulnya ada dan children-nya ter-render. Pakai getByRole di mana bisa. Lalu ubah <aside>-nya jadi <div> di component-nya dan lihat apakah test-mu menyadarinya — kalau tidak, query-mu terlalu longgar.

Hasil yang diharapkan

Sebuah test yang lolos, dan yang gagal saat elemen landmark-nya berubah.