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:
- Arrange (Siapkan): Siapkan data masukan dan kondisi awal.
- Act (Jalankan): Panggil fungsi yang sedang diuji.
- 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 Asersi | Kegunaan |
|---|---|
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" }
);
});
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:
- Buat fungsi
hitungKeranjang(daftarItem)yang menghitung total harga dari array{ nama, harga, qty }dan melemparRangeErrorjika adaharga < 0atauqty <= 0. - Tulis minimal 4 skenario pengujian menggunakan
describedanit: keranjang kosong menghasilkan0, keranjang normal menghasilkan total yang benar, item denganqtynegatif melemparRangeError, dan pengujian fungsi asinkronsimpanKeranjang()menggunakant.mock.fn().