Kodeloka
  1. Kodeloka
  2. Kelas JavaScript
  3. Materi

6 · Keamanan & Kualitas · Modul 20 dari 22

Pengujian Otomatis JavaScript dengan node:test dan node:assert

Membangun jaring pengaman kualitas perangkat lunak dengan menulis Unit Test dan Integration Test menggunakan test runner bawaan node:test dan node:assert/strict, pola Arrange-Act-Assert, menguji kode asinkron/galat, serta memeriksa perilaku situs di peramban.

Mengapa Pengujian Otomatis Membuat Pengembangan Lebih Cepat?

Menguji aplikasi hanya dengan mengklik-klik layar secara manual setiap kali Anda mengubah kode akan menjadi beban yang sangat lambat begitu fitur bertambah banyak. Dengan **pengujian otomatis (automated tests), Anda dapat memverifikasi ratusan skenario perhitungan, validasi, dan penanganan galat hanya dalam waktu kurang dari satu detik melalui perintah npm test**.

Kabar terbaik di ekosistem JavaScript modern: sejak Node.js 18 (berstatus stabil sejak Node.js 20), Anda tidak perlu lagi menginstal pustaka pihak ketiga yang berat hanya untuk menjalankan pengujian unit! Runtime Node.js sudah menyediakan modul bawaan node:test dan node:assert/strict (sebagai pembanding, repositori Kodeloka sendiri memakai node:assert/strict pada berkas tests/site.test.mjs yang dijalankan lewat npm test).

1. Struktur Pengujian dengan Pola Arrange – Act – Assert (AAA)

Setiap pengujian yang baik mengikuti tiga langkah sederhana:

  1. Arrange (Siapkan): Siapkan data masukan dan kondisi awal.
  2. Act (Jalankan): Panggil fungsi yang sedang diuji.
  3. Assert (Pastikan): Periksa apakah hasil keluarannya sama persis dengan yang diharapkan menggunakan node:assert/strict.

Mari kita lihat contoh modul logika bisnis beserta berkas pengujiannya:

// Berkas: ./kuis.js
export function hitungNilaiKuis(jawabanPengguna, kunciJawaban) {
  if (!Array.isArray(jawabanPengguna) || !Array.isArray(kunciJawaban)) {
    throw new TypeError("Jawaban dan kunci wajib berupa Array.");
  }
  if (kunciJawaban.length === 0) {
    throw new RangeError("Kunci jawaban tidak boleh kosong.");
  }

  let benar = 0;
  for (const [indeks, kunci] of kunciJawaban.entries()) {
    if (jawabanPengguna[indeks] === kunci) {
      benar++;
    }
  }

  const skor = Math.round((benar / kunciJawaban.length) * 100);
  return {
    benar,
    total: kunciJawaban.length,
    skor,
    lulus: skor >= 75
  };
}

Sekarang kita tulis berkas pengujiannya di kuis.test.js:

// Berkas: ./kuis.test.js
// Jalankan langsung di terminal dengan perintah: node --test kuis.test.js
import { describe, it } from "node:test";
import assert from "node:assert/strict";
import { hitungNilaiKuis } from "./kuis.js";

describe("hitungNilaiKuis()", () => {
  it("menghitung skor 100 dan status lulus saat semua jawaban benar", () => {
    // 1. Arrange
    const kunci = ["A", "B", "C", "D"];
    const jawaban = ["A", "B", "C", "D"];

    // 2. Act
    const hasil = hitungNilaiKuis(jawaban, kunci);

    // 3. Assert (deepEqual pada assert/strict memeriksa kesamaan isi objek secara ketat)
    assert.deepEqual(hasil, {
      benar: 4,
      total: 4,
      skor: 100,
      lulus: true
    });
  });

  it("menandai tidak lulus jika skor di bawah 75", () => {
    const hasil = hitungNilaiKuis(["A", "X", "X", "D"], ["A", "B", "C", "D"]);
    assert.equal(hasil.skor, 50);
    assert.equal(hasil.lulus, false);
  });

  it("melempar RangeError jika array kunci jawaban kosong", () => {
    // Menguji bahwa fungsi benar-benar melempar galat yang tepat!
    assert.throws(
      () => hitungNilaiKuis([], []),
      {
        name: "RangeError",
        message: /tidak boleh kosong/
      }
    );
  });
});

2. Metode Asersi Penting di node:assert/strict

Selalu impor dari "node:assert/strict" agar semua pemeriksaan menggunakan kesetaraan ketat (=== / Object.is):

Metode AsersiKegunaan
assert.equal(aktual, ekspektasi)Memastikan dua nilai primitif (number, string, boolean) sama persis (===).
assert.deepEqual(objAktual, objEkspektasi)Membandingkan kesamaan isi Object, Array, Map, atau Set secara rekursif.
assert.ok(nilai)Memastikan nilai bernilai truthy.
assert.match(teks, /regex/)Memastikan string cocok dengan ekspresi reguler.
assert.throws(fn, detailGalat)Memastikan fungsi sinkron fn melempar galat yang sesuai.
await assert.rejects(promiseAtauAsyncFn, detailGalat)Memastikan Promise atau fungsi async ditolak (rejected) dengan galat yang sesuai!

3. Menguji Kode Asinkron dan Mocking dengan t.mock

Bagaimana cara menguji fungsi async yang memanggil fetch() tanpa harus bergantung pada koneksi internet sungguhan saat pengujian berjalan di server CI/CD?

Modul node:test menyediakan fitur Mocking bawaan (t.mock) untuk memalsukan fungsi atau melacak berapa kali sebuah fungsi dipanggil:

import { test } from "node:test";
import assert from "node:assert/strict";

async function ambilJudulModul(id, fungsiFetch = globalThis.fetch) {
  const res = await fungsiFetch(`https://kodeloka.asadinedu.com/api/modul/${id}`);
  if (!res.ok) throw new Error(`Modul ${id} tidak ditemukan`);
  const data = await res.json();
  return data.judul;
}

test("ambilJudulModul() mengembalikan judul saat respons 200 OK", async (t) => {
  // Membuat fungsi fetch tiruan (mock) yang langsung membalas tanpa jaringan
  const fetchTiruan = t.mock.fn(async () => ({
    ok: true,
    status: 200,
    json: async () => ({ id: 1, judul: "Cara Web Bekerja" })
  }));

  const judul = await ambilJudulModul(1, fetchTiruan);

  assert.equal(judul, "Cara Web Bekerja");
  assert.equal(fetchTiruan.mock.callCount(), 1);
});

test("ambilJudulModul() menolak Promise saat status 404", async (t) => {
  const fetchGagal = t.mock.fn(async () => ({ ok: false, status: 404 }));

  // Wajib menggunakan 'await assert.rejects(...)' untuk menguji kegagalan async!
  await assert.rejects(
    async () => await ambilJudulModul(999, fetchGagal),
    { message: "Modul 999 tidak ditemukan" }
  );
});
⚠️ Jebakan Pengujian Asinkron: Lupa Menulis await di Depan assert.rejects()!

assert.rejects() adalah fungsi asinkron yang mengembalikan sebuah Promise. Jika Anda lupa menulis await assert.rejects(...), pengujian akan selesai sebelum Promise di dalamnya sempat diperiksa!

Latihan Praktik: Menulis Suite Pengujian Lengkap untuk Modul Keranjang Belanja

Buatlah berkas keranjang.js dan keranjang.test.js, lalu jalankan dengan node --test keranjang.test.js:

  1. Buat fungsi hitungKeranjang(daftarItem) yang menghitung total harga dari array { nama, harga, qty } dan melempar RangeError jika ada harga < 0 atau qty <= 0.
  2. Tulis minimal 4 skenario pengujian menggunakan describe dan it: keranjang kosong menghasilkan 0, keranjang normal menghasilkan total yang benar, item dengan qty negatif melempar RangeError, dan pengujian fungsi asinkron simpanKeranjang() menggunakan t.mock.fn().