Matcher adalah method yang kamu sambungkan ke expect(...) untuk menyatakan cara membandingkan hasilnya: toBe, toEqual, toContain, dan kawan-kawannya. Satu baris expect(...).matcher(...) disebut assertion, pernyataan yang harus benar. Bayangin kamu menerima paket. Ada dua cara mengeceknya: "ini kotak yang sama dengan yang kukirim, nomor resinya sama?" atau "isinya sama dengan daftar barang yang kupesan?". Dua kotak berbeda bisa berisi barang yang persis sama. toBe bertanya yang pertama, toEqual yang kedua.
🧪 Tebakan cepat:
expect({ a: 1, b: 2 }).toBe({ a: 1, b: 2 }), lulus atau gagal? Jawabannya ada di bagian "toBe vs toEqual".
toBe vs toEqual
const user = { name: "Budi", age: 25 };
expect(user.age).toBe(25); // lulus
expect(user).toEqual({ name: "Budi", age: 25 }); // lulus
expect(user).toBe({ name: "Budi", age: 25 }); // gagal
toBememeriksa apakah dua nilai identik, seperti===(tepatnyaObject.is). Untuk angka, string, dan boolean, nilai yang sama berarti identik, jaditoBe(25)lulus.- Objek dan array beda cerita.
{ name: "Budi", age: 25 }yang ditulis di dalamtoBe(...)adalah objek baru: kotak lain, walau isinya sama. JaditoBegagal. toEqualmembandingkan isinya secara mendalam (deep equality): tiap properti dan isi di dalamnya. Urutan propertinya gak penting.
Vitest melaporkan baris ketiga seperti ini:
× matchers.test.js > user 1ms
→ expected { name: 'Budi', age: 25 } to be { name: 'Budi', age: 25 } // Object.is equality
If it should pass with deep equality, replace "toBe" with "toStrictEqual"
Expected: { name: 'Budi', age: 25 }
Received: serializes to the same string
"serializes to the same string" artinya isinya sama persis, tapi objeknya beda. Jadi tebakan tadi: gagal (FAIL). Itu jawaban tebakannya. Vitest menyarankan toStrictEqual, versi toEqual yang lebih ketat (properti bernilai undefined dan jenis class ikut dicek). Patokannya: toBe untuk angka, string, dan boolean, toEqual untuk objek dan array.
Isi array, objek, dan teks
const fruits = ["apel", "jeruk", "mangga"];
expect(fruits).toContain("apel"); // ada "apel" di dalamnya
expect(fruits).toHaveLength(3); // panjangnya 3
expect(user).toMatchObject({ name: "Budi" }); // sebagian propertinya cocok
expect(user).toHaveProperty("age", 25); // punya properti age bernilai 25
expect("Halo Dunia").toContain("Dunia"); // potongan teks
expect("Halo Dunia").toMatch(/^Halo/); // cocok dengan regex
Kalau gagal, pesannya menyebut isinya: expected [ 'apel', 'jeruk', 'mangga' ] to include 'durian' atau expected [ 'apel', 'jeruk', 'mangga' ] to have a length of 4 but got 3.
Benar-salah, null, dan angka
expect(hasil).toBeTruthy(); // dianggap benar oleh JavaScript
expect(hasil).toBeFalsy(); // false, 0, "", null, undefined, atau NaN
expect(hasil).toBeNull(); // persis null
expect(hasil).toBeUndefined(); // persis undefined
expect(hasil).toBeDefined(); // apa pun selain undefined
expect(10).toBeGreaterThan(5);
expect(3).toBeLessThanOrEqual(3);
expect(0.1 + 0.2).toBeCloseTo(0.3); // lulus
expect(0.1 + 0.2).toBe(0.3); // gagal: hasilnya 0.30000000000000004
Truthy artinya semua nilai selain enam nilai falsy: false, 0, "", null, undefined, dan NaN. Komputer menyimpan desimal dalam bentuk biner, jadi 0.1 + 0.2 meleset sedikit dari 0.3. toBeCloseTo menganggapnya sama selama selisihnya kecil sekali.
toThrow: fungsinya dibungkus
function divide(a, b) {
if (b === 0) throw new Error("Cannot divide by zero");
return a / b;
}
expect(() => divide(10, 0)).toThrow(); // lulus
expect(() => divide(10, 0)).toThrow("Cannot divide by zero"); // lulus, pesannya cocok
expect(divide(10, 0)).toThrow(); // test-nya meledak duluan
() => divide(10, 0)adalah function yang belum dijalankan.expectyang memanggilnya, menangkap error-nya, lalu memeriksanya.- Tanpa pembungkus,
divide(10, 0)jalan duluan, error-nya terlempar sebelumexpectsempat menangkap, dan test gagal denganCannot divide by zero. - Teks di
toThrow("...")dicocokkan sebagai potongan pesan error. Regex juga bisa:toThrow(/zero/).
.not membalik matcher
expect(5).not.toBe(3);
expect(fruits).not.toContain("durian");
expect(hasil).not.toBeNull();
Di balik layar: matcher itu cuma fungsi
Kamu bisa membuat expect versi mini sendiri:
function expect(value) {
return {
toBe(expected) {
return value === expected;
},
toEqual(expected) {
return JSON.stringify(value) === JSON.stringify(expected);
},
not: {
toBe(expected) {
return value !== expected;
},
},
};
}
console.log(expect(5).not.toBe(3)); // true
console.log(expect({ a: 1 }).toEqual({ a: 2 })); // false
expect(value)mengembalikan objek berisi matcher. Tiap matcher masih ingatvalue.JSON.stringifymengubah kedua sisi jadi teks, jadi isinya bisa dibandingkan dengan===.notberisi versi yang hasilnya dibalik, termasuk saat hasil aslinyafalse.
Vitest asli bekerja sedikit beda: matcher yang gagal melempar error, dan error itulah yang bikin test merah. toEqual aslinya juga gak memakai JSON.stringify, karena { a: 1, b: 2 } dan { b: 2, a: 1 } jadi teks yang beda walau isinya sama.
⚠️ Jebakan yang sering ketemu
toBeuntuk objek atau array. Selalu gagal kalau objeknya dibuat terpisah. PakaitoEqual.- Memanggil fungsi langsung di
toThrow.expect(divide(10, 0))meledak sebelum diperiksa. Bungkus:expect(() => divide(10, 0)). toBeuntuk desimal.0.1 + 0.2bukan persis0.3. PakaitoBeCloseTo.- Matcher yang terlalu longgar.
toBeTruthy()juga lulus untuk"error". Pilih yang paling spesifik:toBeNull(),toBe(25),toHaveLength(3).
TL;DR: Matcher menentukan cara expect membandingkan. toBe memeriksa nilai identik (seperti ===) untuk angka, string, dan boolean, toEqual membandingkan isi objek dan array secara mendalam, jadi expect({ a: 1 }).toBe({ a: 1 }) gagal. toContain memeriksa isi array atau teks, toHaveLength panjangnya, toBeTruthy, toBeNull, dan toBeUndefined jenis nilainya, toBeGreaterThan dan toBeCloseTo angka. toThrow butuh fungsi yang dibungkus () =>, dan .not membalik matcher apa pun.