Snapshot Testing — Testing

Apa itu Snapshot Testing? Snapshot testing adalah teknik di mana output dari kode (misalnya HTML yang dirender) disimpan sebagai "foto" dan dibandingkan…

Snapshot testing menyimpan "foto" dari sebuah output, misalnya HTML hasil render komponen, lalu di tiap run berikutnya membandingkan output baru dengan foto itu. Kalau beda, test-nya gagal dan kamu yang memutuskan: perubahan itu disengaja atau bug. Bayangin kamu memotret rak toko sebelum tutup. Besok pagi kamu bandingkan raknya dengan foto: barang yang bergeser langsung ketahuan. Tapi foto gak tahu mana yang sengaja dipindah dan mana yang hilang. Itu tugasmu.

🧪 Tebakan cepat: kamu sengaja mengganti class tombol dari btn-primary jadi btn-utama, dan snapshot test-nya langsung gagal. Yang benar: hapus test-nya, kembalikan class-nya, atau update snapshot-nya? Jawabannya ada di bagian "Kalau gagal: baca diff, lalu putuskan".

Rekam sekali, bandingkan tiap run

Contoh di pelajaran ini dan di Lab: komponen tombol beli.

// TombolBeli.jsx
export function TombolBeli() {
  return (
    <button className="btn btn-primary" type="button">
      Beli
    </button>
  );
}
// TombolBeli.test.jsx
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import { TombolBeli } from "./TombolBeli";

describe("TombolBeli", () => {
  it("tampil sesuai desain", () => {
    const { container } = render(<TombolBeli />);
    expect(container.firstChild).toMatchSnapshot();
  });
});

Snapshot-nya disimpan di folder __snapshots__ di sebelah file test, dan ikut di-commit ke git:

// __snapshots__/TombolBeli.test.jsx.snap
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`TombolBeli > tampil sesuai desain 1`] = `
<button
  class="btn btn-primary"
  type="button"
>
  Beli
</button>
`;

Atributnya diurutkan menurut abjad, satu per baris. className di JSX tercatat sebagai class, karena yang difoto DOM-nya, bukan kode JSX.

Untuk output pendek, ada toMatchInlineSnapshot(). Fotonya ditulis Vitest langsung di dalam file test, jadi gampang dibaca waktu review pull request:

expect(container.firstChild).toMatchInlineSnapshot(`
  <button
    class="btn btn-primary"
    type="button"
  >
    Beli
  </button>
`);

Kalau gagal: baca diff, lalu putuskan

Class-nya diganti jadi btn-utama. Run berikutnya:

 FAIL  TombolBeli.test.jsx > TombolBeli > tampil sesuai desain
Error: Snapshot `TombolBeli > tampil sesuai desain 1` mismatched

- Expected
+ Received

  <button
-   class="btn btn-primary"
+   class="btn btn-utama"
    type="button"
  >
    Beli
  </button>

Jadi baca diff-nya dulu. Kalau perubahannya memang yang kamu maksud, update snapshot-nya:

npx vitest --update

-u adalah singkatannya, dan di mode watch kamu cukup menekan u. Laporannya bilang Snapshots 1 updated. Itu jawaban tebakannya: perubahannya disengaja, jadi update, bukan hapus test. Kalau diff-nya menunjukkan sesuatu yang gak kamu ubah, itu bug: perbaiki kodenya, jangan update.

Nilai yang berubah tiap run

Komponen yang menaruh jam, tanggal, atau id acak di HTML-nya membuat snapshot gagal di setiap run, walau kodenya gak berubah:

<button id={`beli-${Math.random().toString(36).slice(2, 6)}`} ...>

Tiap render id-nya beda, jadi diff-nya selalu merah. Solusinya: kunci nilainya di test (vi.useFakeTimers() dan vi.setSystemTime(...) untuk waktu, mock untuk angka acak), atau jangan masukkan nilai itu ke snapshot.

Kapan snapshot cocok

Risiko terbesarnya: snapshot yang besar dan sering berubah membuat developer terbiasa menekan -u tanpa membaca diff-nya. Perubahan yang gak disengaja ikut tersimpan, dan test-nya jadi gak menjaga apa-apa. Snapshot mendeteksi perubahan, bukan membuktikan kebenaran, jadi pakai sebagai pelengkap assertion biasa.

⚠️ Jebakan yang sering ketemu

TL;DR: Snapshot test merekam output sekali (toMatchSnapshot() ke file .snap di __snapshots__, atau toMatchInlineSnapshot() langsung di file test), lalu membandingkan tiap run berikutnya. Kalau gagal, baca diff-nya (- snapshot lama, + hasil baru): kalau perubahannya disengaja, update dengan npx vitest --update (atau -u), kalau gak, perbaiki kodenya. Nilai yang berubah tiap run (waktu, id acak) harus dikunci. Risiko utamanya: developer terbiasa auto-update tanpa membaca perubahan, jadi pakai snapshot kecil untuk output stabil, sebagai pelengkap assertion biasa.

Yang akan kamu pelajari