Modul node:test adalah runner pengujian bawaan Node yang tidak memerlukan dependensi eksternal, dipanggil lewat flag --test pada perintah node. Topik ini membahas struktur berkas uji, pemakaian node:assert/strict untuk asersi, serta hook siklus hidup before, beforeEach, after, dan afterEach yang dipasang melalui argumen konteks t pada callback tes. Setelah membaca topik ini, sebuah kerangka pengujian dapat disusun hanya dari modul inti Node, dengan keluaran TAP yang dapat dibaca mesin dan manusia.
Apa itu node:test ?#
node:test adalah modul inti yang menjadi runner pengujian resmi sejak Node 18 dan stabil pada Node 20 ke atas. Runner ini dijalankan dengan node --test pada satu berkas, beberapa berkas, atau seluruh berkas yang cocok dengan pola *.test.js di dalam direktori tertentu. Modul ini memuat fungsi test untuk mendefinisikan kasus uji, kelas Suite untuk pengelompokan, serta API describe dan it yang menyerupai Jest untuk pembaca yang sudah terbiasa dengan pola tersebut.
Berbeda dari pustaka pihak ketiga, node:test membawa reporter TAP sebagai default bila outputnya dialihkan ke pipe, dan reporter spec yang berwarna bila ditulis langsung ke terminal. Asersi disediakan oleh modul terpisah, yaitu node:assert, sehingga runner tidak memaksakan pustaka asersi tertentu dan pembaca bebas memakai node:assert/strict, chai, atau pustaka lain. Pemisahan runner dan asersi ini menyederhanakan upgrade versi Node karena seluruh rantai pengujian tetap berada di dalam modul inti.
Menulis tes pertama dengan node:test#
Sebuah berkas uji pada node:test memuat require('node:test') untuk mendapatkan fungsi test, lalu memanggil test(nama, callback) sekali untuk setiap kasus uji. Nama tes ditampilkan apa adanya pada keluaran reporter, sehingga deskripsi yang menyatakan perilaku yang diharapkan membantu saat menelusuri kegagalan. Callback tes dapat berbentuk fungsi sinkron atau async, dan kesalahan yang dilempar di dalamnya otomatis menandai tes sebagai gagal.
Berkas math.test.js berikut menguji tiga fungsi pada math.js, yaitu tambah, kurang, dan bagi. Setiap tes memanggil satu fungsi, melakukan asersi assert.equal atau assert.throws, lalu kembali. Pemisahan satu kasus uji per pemanggilan test menjaga agar kegagalan pada satu kasus tidak menyembunyikan kegagalan kasus lain.
function tambah(a, b) {
return a + b;
}
function kurang(a, b) {
return a - b;
}
function bagi(a, b) {
if (b === 0) throw new Error('pembagi tidak boleh nol');
return a / b;
}
module.exports = { tambah, kurang, bagi };// tidak-dijalankan: butuh sibling math.js di direktori yang sama dengan berkas sumber
const test = require('node:test');
const assert = require('node:assert/strict');
const { tambah, kurang, bagi } = require('./math');
test('penjumlahan dua bilangan positif', () => {
assert.equal(tambah(2, 3), 5);
});
test('pengurangan dapat menghasilkan nilai negatif', () => {
assert.equal(kurang(2, 5), -3);
});
test('pembagian oleh nol melempar error', () => {
assert.throws(() => bagi(10, 0), /pembagi tidak boleh nol/);
});
test('pembagian mengembalikan nilai desimal', () => {
assert.equal(bagi(7, 2), 3.5);
});Eksekusi dilakukan dengan node --test pada berkas uji tersebut. Reporter TAP dipilih secara eksplisit agar keluaran stabil untuk dibandingkan, lalu blok YAML yang memuat duration_ms disaring dengan sed agar nilainya tidak berbeda antar eksekusi.
# jalankan dari direktori topik
node --test --test-reporter tap math.test.js 2>&1 | sed -E '/^[[:space:]]+---$/,/^[[:space:]]+\.\.\.$/d; /^# duration_ms/d'TAP version 13
# Subtest: penjumlahan dua bilangan positif
ok 1 - penjumlahan dua bilangan positif
# Subtest: pengurangan dapat menghasilkan nilai negatif
ok 2 - pengurangan dapat menghasilkan nilai negatif
# Subtest: pembagian oleh nol melempar error
ok 3 - pembagian oleh nol melempar error
# Subtest: pembagian mengembalikan nilai desimal
ok 4 - pembagian mengembalikan nilai desimal
1..4
# tests 4
# suites 0
# pass 4
# fail 0
# cancelled 0
# skipped 0
# todo 0Baris ok 1 sampai ok 4 menyatakan keberhasilan tiap kasus uji, sedangkan 1..4 adalah rentang TAP yang menyatakan jumlah tes. Baris # pass 4 dan # fail 0 di akhir adalah ringkasan yang ditambahkan runner di luar format TAP murni, tetapi keduanya berguna untuk pemeriksaan cepat pada log CI.
Asersi pada node:assert/strict#
Modul node:assert menyediakan fungsi asersi yang berhenti dengan pengecualian AssertionError bila kondisi tidak terpenuhi. Versi node:assert/strict memakai pembanding ketat (===) alih-alih pembanding longgar (==) yang dimiliki assert biasa, sehingga assert.equal(1, '1') menjadi kegagalan, bukan kelulusan. Pemakaian versi strict merupakan praktik yang dianjurkan karena menghilangkan konversi tipe implisit yang sering menyembunyikan bug.
Beberapa asersi yang sering dipakai pada tes Node adalah assert.equal untuk kesamaan primitif, assert.deepEqual untuk kesamaan struktur objek secara rekursif, assert.ok untuk nilai kebenaran, serta assert.throws untuk memastikan sebuah fungsi melempar error. Asersi assert.rejects berperilaku sama tetapi menerima promise yang ditolak. Keluarga notEqual, notDeepEqual, dan doesNotThrow juga tersedia sebagai kebalikannya.
Asersi tambahan seperti assert.match(string, regexp) dan assert.doesNotMatch berguna untuk memeriksa pola tanpa menulis RegExp.test manual. Pemakaian asersi bawaan node:assert/strict sebaiknya dipertahankan kecuali bila kelompok pengujian sudah memiliki konvensi pada pustaka seperti chai, karena percampuran asersi dari dua pustaka memperbesar biaya pembacaan kode.
Hook siklus hidup: before, beforeEach, after, afterEach#
node:test menyediakan empat hook yang dipasang melalui argumen konteks t pada callback test. Hook t.before(fn) berjalan satu kali sebelum seluruh subtes dari tes induk, sedangkan t.after(fn) berjalan satu kali setelah seluruh subtes selesai. Hook t.beforeEach(fn) dan t.afterEach(fn) berjalan sebelum dan sesudah setiap subtes secara terpisah. Hook tidak berlaku untuk tes di luar lingkup induk yang mendeklarasikannya, sehingga pemodelan ulang tes ke dalam subtes juga mengubah lingkup pembersihan.
Berkas counter.test.js berikut memakai t.before dan t.beforeEach untuk menjaga agar modul counter.js selalu dimulai dari nilai nol. Tanpa t.beforeEach, tes kedua akan melihat sisa nilai dari tes pertama karena counter.js menyimpan state pada variabel modul.
let nilai = 0;
function reset() {
nilai = 0;
}
function tambah(n) {
nilai += n;
return nilai;
}
function ambil() {
return nilai;
}
module.exports = { reset, tambah, ambil };// tidak-dijalankan: butuh sibling counter.js di direktori yang sama dengan berkas sumber
const test = require('node:test');
const assert = require('node:assert/strict');
const { reset, tambah, ambil } = require('./counter');
test('hook before dan beforeEach memengaruhi state counter', async (t) => {
t.before(() => reset());
t.beforeEach(() => reset());
await t.test('subtes pertama menambah 3', () => {
assert.equal(tambah(3), 3);
});
await t.test('subtes kedua mulai dari nol akibat beforeEach', () => {
assert.equal(ambil(), 0);
assert.equal(tambah(10), 10);
});
});# jalankan dari direktori topik
node --test --test-reporter tap counter.test.js 2>&1 | sed -E '/^[[:space:]]+---$/,/^[[:space:]]+\.\.\.$/d; /^# duration_ms/d'TAP version 13
# Subtest: hook before dan beforeEach memengaruhi state counter
# Subtest: subtes pertama menambah 3
ok 1 - subtes pertama menambah 3
# Subtest: subtes kedua mulai dari nol akibat beforeEach
ok 2 - subtes kedua mulai dari nol akibat beforeEach
1..2
ok 1 - hook before dan beforeEach memengaruhi state counter
1..1
# tests 3
# suites 0
# pass 3
# fail 0
# cancelled 0
# skipped 0
# todo 0Indentasi pada baris # Subtest dan ok menunjukkan kedalaman bersarang. Subtes subtes pertama menambah 3 dan subtes kedua mulai dari nol akibat beforeEach berada di dalam tes induk hook before dan beforeEach memengaruhi state counter. Angka pada ok 1 dan ok 2 dihitung per lingkup, sedangkan ringkasan akhir # tests 3 menghitung total satu tes induk ditambah dua subtes. Tanpa t.beforeEach, subtes kedua akan melihat ambil() bernilai 3, sehingga assert.equal(ambil(), 0) akan gagal.
Kesalahan umum#
Memakai assert.equal untuk membandingkan objek. assert.equal membandingkan referensi, bukan isi, sehingga dua objek dengan isi yang sama tetap dianggap berbeda. Gunakan assert.deepEqual atau assert.deepStrictEqual untuk objek dan array.
Lupa meng-await t.test di dalam tes async. Tanpa await, subtes tidak menunggu eksekusi selesai sebelum tes induk kembali, sehingga kegagalan subtes tidak tertangkap dan hook after dapat berjalan sebelum subtes selesai. Pasang await pada setiap t.test di dalam test(async (t) => { ... }).
Mengandalkan urutan eksekusi tes untuk berbagi state. node:test menjalankan tes secara berurutan secara default, tetapi kontrak ini hanya berlaku pada level berkas dan bukan jaminan logika. Bila sebuah tes bergantung pada efek samping tes lain, pindahkan state bersama ke hook before atau beforeEach agar ketergantungan menjadi eksplisit.
Ringkasan#
node:testadalah runner bawaan Node yang dipanggil lewatnode --test, dengan asersi dari modul terpisahnode:assert/strictyang memakai pembanding ketat.- Fungsi
test(nama, callback)mendefinisikan kasus uji, dan callback dapat berupa fungsi sinkron maupunasyncyang melempar error untuk menandai kegagalan. - Hook
t.before,t.beforeEach,t.after, dant.afterEachdipasang lewat argumen kontekst, berlaku pada subtes dari tes yang mendeklarasikannya, dan berguna untuk membangun serta membersihkan state bersama. - Reporter TAP memberikan keluaran yang stabil untuk CI, sedangkan reporter
specmemberikan tampilan yang lebih ramah pembaca pada terminal.
Referensi#
- Test runner — Node.js
— Referensi resmi modul
node:testmencakuptest, hook siklus hidup, dan reporter TAP. - Assert — Node.js
— Daftar lengkap asersi pada
node:assertdannode:assert/strict, termasukdeepEqual,throws, danrejects.