Matcher & Assertions — Testing

Apa itu Matcher? Matcher adalah method yang digunakan untuk memverifikasi apakah suatu nilai sesuai ekspektasi. Vitest (dan Jest) menyediakan banyak matcher…

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

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

.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

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

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.

Yang akan kamu pelajari