Test runner yang baik hanya separuh cerita; test tetap tidak berarti tanpa cara menyatakan harapan. Assertion adalah pernyataan harapan itu: satu baris kode yang membandingkan nilai nyata hasil eksekusi dengan nilai yang kita yakini benar, lalu melempar error jika keduanya tidak sama. Module bawaan node:assert menyediakan belasan function assertion dengan aturan perbandingan yang berbeda-beda, dan memilih yang tepat menentukan apakah test kita menangkap kesalahan sungguhan atau justru menyembunyikannya. Pada Chapter ini kita membedah yang paling sering dipakai, termasuk jebakan klasik antara equal dan strictEqual.

Peran Assertion dalam Test #

Semua assertion node:assert bekerja dengan mekanisme yang sama: bandingkan, dan lempar AssertionError jika perbandingan tidak terpenuhi. Test runner node:test menangkap error tersebut, menandai test sebagai gagal, lalu melanjutkan ke test berikutnya — satu kegagalan tidak menghentikan seluruh suite. Karena itu pola paling ringkas dalam sebuah test selalu berupa assert.sesuatu(nilaiNyata, nilaiHarapan): jika baris itu lolos tanpa error, satu janji perilaku terbukti.

Bentuk paling dasar adalah assert.ok(nilai), yang hanya memeriksa apakah nilainya truthy. Argumen kedua yang opsional berisi pesan kustom — pesan inilah yang tampil ketika assertion gagal, sehingga pesan yang ditulis baik menghemat waktu diagnosis:

js
import assert from 'node:assert';

const sisaStok = 0;

try {
  assert.ok(sisaStok > 0, 'stok harus tersisa saat checkout');
} catch (error) {
  console.log('tertangkap:', error.message);
}

Output yang Dihasilkan:

text
tertangkap: stok harus tersisa saat checkout

equal vs strictEqual #

Dua nama yang mirip ini memakai dua aturan perbandingan yang berbeda. assert.equal memakai operator longgar == yang bersedia mengkonversi tipe sebelum membandingkan, sedangkan strictEqual memakai === yang menuntut nilai dan tipe sama persis. Perbedaan ini terasa nyata pada angka versus string:

Perbandingan== (equal)=== (strictEqual)
1 dan '1'Sama — string dikonversi jadi angkaBerbeda — number bukan string
0 dan ''Sama — keduanya falsy setelah konversiBerbeda
null dan undefinedSamaBerbeda
[1] dan ['1'] via deepEqualSama — isi dibandingkan longgarBerbeda via deepStrictEqual

Mari kita buktikan tabel tersebut dengan kode yang menangkap hasil setiap assertion:

js
import assert from 'node:assert';

function cek(nama, pembanding) {
  try {
    pembanding();
    console.log(nama, ': lolos');
  } catch {
    console.log(nama, ': gagal');
  }
}

cek('assert.equal(1, "1")', () => assert.equal(1, '1'));
cek('assert.strictEqual(1, "1")', () => assert.strictEqual(1, '1'));
cek('assert.deepEqual([1], ["1"])', () => assert.deepEqual([1], ['1']));
cek('assert.deepStrictEqual([1], ["1"])', () => assert.deepStrictEqual([1], ['1']));

Output yang Dihasilkan:

text
assert.equal(1, "1") : lolos
assert.strictEqual(1, "1") : gagal
assert.deepEqual([1], ["1"]) : lolos
assert.deepStrictEqual([1], ["1"]) : gagal

Baris pertama dan ketiga lolos karena perbandingan longgar menerima "1" sebagai pengganti 1 — perilaku yang berbahaya dalam test. Bayangkan function hitungTotal tanpa sengaja mengembalikan string "195000"; test dengan assert.equal tetap hijau padahal tipe data sudah salah dan bisa merusak konsumen API kita di tempat lain. Aturan praktisnya sederhana: selalu pilih varian strict.

deepStrictEqual untuk Object dan Array #

Perbandingan === pada object hanya membandingkan referensi memori: dua object dengan isi identik tetap dianggap berbeda. Untuk membandingkan isi, node:assert menyediakan deepStrictEqual yang menelusuri struktur secara rekursif — array di dalam object, object di dalam array — sambil tetap menjaga ketatnya tipe data. Pasangannya, deepEqual, melakukan hal yang sama dengan perbandingan longgar di setiap tingkat; sebagaimana tabel di atas, varian strict-lah yang kita pilih.

assert.throws dan assert.rejects #

Kode yang benar tidak hanya mengembalikan nilai benar — kode tersebut juga harus gagal dengan cara yang benar. assert.throws memanggil sebuah function sinkron dan memeriksa bahwa panggilan tersebut melempar error yang kita antisipasi. Argumen keduanya bisa berupa class error, regular expression untuk pesan, atau object berisi properti yang harus cocok. Untuk operasi asynchronous, assert.rejects melakukan hal serupa terhadap promise yang ditolak:

js
import assert from 'node:assert';

function ambilProfil(id) {
  return id > 0
    ? Promise.resolve({ id, nama: 'Budi' })
    : Promise.reject(new Error('id tidak valid'));
}

await assert.rejects(ambilProfil(-1), { message: 'id tidak valid' });
console.log('penolakan sesuai harapan');

Output yang Dihasilkan:

text
penolakan sesuai harapan

assert.rejects mengembalikan promise, sehingga wajib di-await di dalam test async — jika tidak, kegagalan penolakan lolos tanpa terdeteksi. Detail perilaku ini dibahas lebih lanjut pada Chapter Menguji Kode Asynchronous.

Assertion dalam Test Utuh #

Saatnya merangkai seluruh perkenalan menjadi satu berkas test yang lengkap. Module keranjang.mjs berisi tiga function dengan tiga karakter berbeda: perhitungan angka, pencarian object, dan validasi yang melempar error.

js
export function hitungTotal(items) {
  if (items.length === 0) {
    return 0;
  }
  return items.reduce((total, item) => total + item.harga * item.jumlah, 0);
}

export function cariItem(items, nama) {
  return items.find((item) => item.nama === nama) ?? null;
}

export function validasiItem(item) {
  if (!item.nama || item.harga <= 0) {
    throw new Error('nama dan harga item wajib valid');
  }
  return true;
}
js
import { test } from 'node:test';
import assert from 'node:assert';
import { hitungTotal, cariItem, validasiItem } from './keranjang.mjs';

test('menghitung total belanja dari daftar item', () => {
  const items = [
    { nama: 'Buku Node.js', harga: 90000, jumlah: 2 },
    { nama: 'Pena', harga: 5000, jumlah: 3 },
  ];
  assert.strictEqual(hitungTotal(items), 195000);
});

test('keranjang kosong menghasilkan total nol', () => {
  assert.strictEqual(hitungTotal([]), 0);
});

test('pencarian item mengembalikan object utuh', () => {
  const items = [{ nama: 'Pena', harga: 5000, jumlah: 3 }];
  assert.deepStrictEqual(cariItem(items, 'Pena'), {
    nama: 'Pena',
    harga: 5000,
    jumlah: 3,
  });
});

test('item yang tidak ada mengembalikan null', () => {
  assert.strictEqual(cariItem([], 'Spidol'), null);
});

test('validasi menolak item tanpa nama', () => {
  assert.throws(
    () => validasiItem({ nama: '', harga: 5000 }),
    { message: 'nama dan harga item wajib valid' }
  );
});

test('validasi menerima item yang lengkap', () => {
  assert.ok(validasiItem({ nama: 'Spidol', harga: 12000 }), 'item lengkap dianggap valid');
});
bash
node --test keranjang.test.mjs

Output yang Dihasilkan:

text
✔ menghitung total belanja dari daftar item
✔ keranjang kosong menghasilkan total nol
✔ pencarian item mengembalikan object utuh
✔ item yang tidak ada mengembalikan null
✔ validasi menolak item tanpa nama
✔ validasi menerima item yang lengkap
ℹ tests 6
ℹ suites 0
ℹ pass 6
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

Enam test pada berkas tersebut memperlihatkan pemetaan sederhana antara karakter kode dan pilihan assertion:

  1. Test 1-2 (perhitungan angka): strictEqual membandingkan angka hasil dengan angka harapan — tipe number terjaga.
  2. Test 3 (object hasil pencarian): deepStrictEqual membandingkan isi object secara rekursif karena === hanya membandingkan referensi.
  3. Test 4 (nilai kosong): strictEqual juga tepat untuk null, yang bukan anggota himpunan truthy maupun falsy yang ambigu.
  4. Test 5 (error yang diantisipasi): assert.throws menerima callback pembungkus dan object { message: ... } sehingga hanya pesan yang benar yang diterima.
  5. Test 6 (kondisi boolean): assert.ok dengan pesan kustom — varian paling ringkas untuk pernyataan benar/salah.

Membaca Kegagalan Assertion #

Kegagalan adalah momen paling penting dari sebuah test, jadi kita perlu terbiasa membaca laporannya. Berkas berikut sengaja memuat harapan yang salah: total seharusnya 180000 (satu buku 90000 dikali jumlah 2), tetapi test menuntut 90000:

js
import { test } from 'node:test';
import assert from 'node:assert';
import { hitungTotal } from './keranjang.mjs';

test('total memperhitungkan jumlah setiap item', () => {
  const items = [{ nama: 'Buku Node.js', harga: 90000, jumlah: 2 }];
  assert.strictEqual(hitungTotal(items), 90000);
});
bash
node --test kegagalan.test.mjs
echo "kode keluar: $?"

Output yang Dihasilkan:

text
✖ total memperhitungkan jumlah setiap item
ℹ tests 1
ℹ suites 0
ℹ pass 0
ℹ fail 1
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0

✖ failing tests:

test at kegagalan.test.mjs:5:1
✖ total memperhitungkan jumlah setiap item
  AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:

  180000 !== 90000

    generatedMessage: true,
    code: 'ERR_ASSERTION',
    actual: 180000,
    expected: 90000,
    operator: 'strictEqual',
    diff: 'simple'
  }
kode keluar: 1

Bagian penting dari laporan kegagalan tersebut:

  1. Baris 180000 !== 90000: nilai di kiri adalah actual — hasil nyata dari hitungTotal(items) — sedangkan di kanan adalah expected yang kita tulis di test. Membaca urutan ini dengan benar langsung menjawab pertanyaan “kode yang salah atau testnya yang salah?”.
  2. Baris test at kegagalan.test.mjs:5:1: lokasi deklarasi test yang gagal, dan jejak stack menunjuk baris assertion tepat di dalam berkas tersebut.
  3. code: 'ERR_ASSERTION': penanda bahwa error berasal dari assertion yang tidak terpenuhi, bukan dari error sintaks atau error runtime lain.
  4. Baris kode keluar: 1: test runner keluar dengan kode 1 — sinyal yang dipakai pipeline CI untuk menghentikan proses build.

Mode strict pada node:assert #

Satu fasilitas terakhir membuat disiplin strict lebih ringan diterapkan: assert.strict adalah salinan module dengan seluruh perbandingan longgar dipetakan ke versi ketatnya — assert.strict.equal adalah strictEqual, dan assert.strict.deepEqual adalah deepStrictEqual. Tim yang ingin menghapus varian longgar sepenuhnya cukup mengimpor node:assert/strict sebagai assert, lalu memakai assert.equal seperti biasa; hasilnya tetap perbandingan ketat. Daftar lengkap seluruh assertion — termasuk assert.match, assert.ifError, dan assert.doesNotThrow — tersedia pada dokumentasi node:assert , dengan detail masing-masing di strictEqual , deepStrictEqual , dan throws .

Ke Mana Setelah Ini #

Kini kita memiliki kosakata lengkap untuk menyatakan harapan: ok untuk boolean, strictEqual untuk nilai primitif, deepStrictEqual untuk struktur data, serta throws dan rejects untuk error. Semua contoh sejauh ini sinkron dan cepat, padahal kode Node.js yang sesungguhnya dipenuhi promise, timer, dan callback. Chapter Menguji Kode Asynchronous membawa perangkat assertion ini ke dunia asynchronous — dimulai dari aturan dasarnya: test async adalah function yang mengembalikan promise.