Component Testing — Testing

Apa itu Component Testing? Component testing adalah pengujian komponen UI (React, Vue, dsb.) secara terisolasi — tanpa menjalankan seluruh aplikasi. Kita…

Apa itu Component Testing?

Component testing adalah pengujian komponen UI (React, Vue, dsb.) secara terisolasi — tanpa menjalankan seluruh aplikasi. Kita merender komponen ke dalam DOM virtual, lalu berinteraksi dan mengecek hasilnya.

Mengapa Component Testing Penting?

Setup: Vitest + Testing Library

Kombinasi Vitest sebagai test runner dan @testing-library/react untuk merender & query komponen adalah standar industri saat ini.

npm install -D @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom

Konfigurasi vitest.config.ts:

import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "jsdom",
    setupFiles: ["./src/test/setup.ts"],
  },
});

Render & Query

Gunakan render() untuk merender komponen, lalu screen untuk mencari elemen:

import { render, screen } from "@testing-library/react";
import Greeting from "./Greeting";

it("menampilkan pesan salam", () => {
  render(<Greeting name="Budi" />);
  expect(screen.getByText("Halo, Budi!")).toBeInTheDocument();
});

Query yang Sering Digunakan

Query Hierarchy — Pilih Query yang Tepat

Testing Library punya aturan prioritas yang mencerminkan bagaimana user menemukan elemen. Semakin tinggi, semakin aksesibel dan realistis:

  1. getByRole — paling direkomendasikan. Mencerminkan bagaimana assistive tech (screen reader) menavigasi halaman. Selalu coba ini dulu.
  2. getByLabelText — untuk form. Mencerminkan bagaimana user mengisi input berdasarkan label.
  3. getByPlaceholderText — hanya jika tidak ada label (hindari di produksi).
  4. getByText — untuk konten non-interaktif: paragraf, heading, tombol tanpa role eksplisit.
  5. getByDisplayValue — untuk form yang sudah terisi.
  6. getByAltText — khusus <img> dan elemen dengan alt.
  7. getByTitle — fallback untuk title attribute.
  8. getByTestIdlast resort. Pakai hanya kalau elemen tidak bisa dijangkau cara lain.

Tiga Varian: getBy, queryBy, findBy

Setiap query punya tiga varian yang behaviour-nya sangat berbeda — salah pilih, test-mu bisa menyesatkan:

VarianTidak ketemuAsync?Kapan pakai
getBy*Lempar errorTidakElemen harus ada sekarang
queryBy*Return nullTidakAssert elemen tidak ada (expect(...).toBeNull())
findBy*Retry sampai timeout lalu errorYa (await)Elemen muncul setelah async (fetch, delay)
// getBy — elemen harus ada SEKARANG
expect(screen.getByRole("heading")).toBeInTheDocument();

// queryBy — assert elemen TIDAK ada
expect(screen.queryByText("Error")).toBeNull();

// findBy — tunggu elemen muncul
const data = await screen.findByText("Data loaded");

userEvent vs fireEvent

Dua cara mensimulasikan interaksi — beda tingkat realismenya:

// fireEvent — satu event, sering miss bug
fireEvent.change(input, { target: { value: "halo" } });

// userEvent — rangkaian event realistis (DIREKOMENDASIKAN)
const user = userEvent.setup();
await user.type(input, "halo"); // mengetik huruf per huruf

Pakai userEvent sebagai default. fireEvent hanya jika butuh trigger event spesifik yang userEvent tidak sediakan (misal: scroll).

Interaksi User dengan userEvent

userEvent mensimulasikan interaksi user yang lebih realistis dibanding fireEvent:

import userEvent from "@testing-library/user-event";

it("mengirim form saat tombol diklik", async () => {
  const user = userEvent.setup();
  const onSubmit = vi.fn();
  render(<LoginForm onSubmit={onSubmit} />);

  await user.type(screen.getByLabelText("Email"), "[email protected]");
  await user.type(screen.getByLabelText("Password"), "rahasia123");
  await user.click(screen.getByRole("button", { name: "Masuk" }));

  expect(onSubmit).toHaveBeenCalledWith({
    email: "[email protected]",
    password: "rahasia123",
  });
});

Testing Async: waitFor

Untuk komponen yang melakukan fetch data atau update async, gunakan waitFor atau findBy*:

it("menampilkan data setelah loading", async () => {
  render(<UserProfile userId={1} />);

  // Awalnya tampil loading
  expect(screen.getByText("Memuat...")).toBeInTheDocument();

  // Tunggu data muncul
  const name = await screen.findByText("Budi Santoso");
  expect(name).toBeInTheDocument();

  // Loading sudah hilang
  expect(screen.queryByText("Memuat...")).not.toBeInTheDocument();
});

Best Practices

Yang akan kamu pelajari