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-primaryjadibtn-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();
});
});
render(...)mengembalikancontainer, elemen pembungkus hasil render.container.firstChildadalah<button>-nya.toMatchSnapshot()membandingkan elemen itu dengan snapshot yang tersimpan.- Run pertama belum punya snapshot, jadi Vitest menulisnya dan test-nya lulus. Laporannya bilang
Snapshots 1 written.
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>
- Baris
-ada di snapshot lama, baris+ada di hasil render baru. Baris tanpa tanda sama di keduanya. - Snapshot cuma bilang "beda". Dia gak tahu mana yang benar.
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
- Cocok: output kecil yang stabil dan perubahannya harus disengaja, misalnya markup satu komponen kecil, pesan error validasi, atau object konfigurasi.
- Kurang cocok: logika bisnis (
expect(hitungDiskon(200000)).toBe(180000)jauh lebih jelas), halaman besar ratusan baris, dan data yang berubah-ubah.
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
- Update tanpa membaca diff.
npx vitest -umenerima semua perubahan, termasuk bug. - Snapshot satu halaman penuh. Diff-nya terlalu panjang untuk dibaca, jadi akhirnya cuma di-update.
- Nilai acak atau waktu di output. Snapshot gagal tiap run. Kunci nilainya dulu.
- Snapshot sebagai satu-satunya test. Snapshot lulus juga untuk HTML yang salah dari awal, kalau yang salah itu yang terekam.
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.