Membangun kebiasaan testing menjadi berat jika langkah pertamanya adalah memilih package, membandingkan framework, dan menyetel konfigurasi. Node.js menghapus alasan tersebut sejak versi 18: runtime ini sudah membawa test runner sendiri bernama node:test — tanpa instalasi, tanpa konfigurasi, dan mengikuti kita ke proyek mana pun. Pada Chapter ini kita mengenal seluruh perangkat dasar runner tersebut: mendeklarasikan test dengan test(), mengelompokkannya dengan describe() dan it(), menyusun subtest, menandai test yang belum siap, dan mengatur kode persiapan lewat hook.
Test Pertama dengan node:test #
Test runner ini dipakai lewat dua module: node:test menyediakan function test untuk mendeklarasikan kasus uji, sementara node:assert menyediakan pembanding hasil. Kode yang diuji kita letakkan di module terpisah agar test dan logika tidak bercampur. Perhatikan dua berkas berikut:
export function tambah(a, b) {
return a + b;
}
export function bagi(a, b) {
if (b === 0) {
throw new Error('pembagi tidak boleh nol');
}
return a / b;
}import { test } from 'node:test';
import assert from 'node:assert';
import { tambah, bagi } from './kalkulator.mjs';
test('tambah menjumlahkan dua angka', () => {
assert.strictEqual(tambah(2, 3), 5);
});
test('bagi membagi angka pertama dengan angka kedua', () => {
assert.strictEqual(bagi(10, 4), 2.5);
});Jalankan berkas test tersebut secara eksplisit:
node --test dasar.test.mjsOutput yang Dihasilkan:
✔ tambah menjumlahkan dua angka
✔ bagi membagi angka pertama dengan angka kedua
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0Tanda ✔ menandai test yang lulus, sedangkan rangkuman di bawahnya menghitung jumlah test, suite, dan hasilnya. Daftar lengkap opsi test() dapat dibaca pada dokumentasi node:test
. Mari kita bedah bagian-bagian penting berkas test di atas:
- Baris 1-2: kedua module inti testing diimpor dengan prefix
node:— konvensi yang juga dipakai chapter-chapter sebelumnya untuk modul bawaan. - Baris 3: function dari
kalkulator.mjsdiimpor sebagaimana module biasa; runner tidak menuntut bentuk khusus dari kode yang diuji. - Baris 5 dan 9: setiap panggilan
test()mendaftarkan satu kasus uji dengan nama yang menjelaskan perilaku yang dijamin — bukan nama function yang dijalankan. - Baris 6 dan 10:
assert.strictEqual(nilaiNyata, nilaiHarapan)membandingkan dengan perbandingan ketat===; ketidakcocokan apa pun membuat test gagal.
Tiga Cara Menjalankan Test #
| Perintah | Perilaku |
|---|---|
node --test dasar.test.mjs | Menjalankan berkas yang disebut saja |
node dasar.test.mjs | Menjalankan berkas itu juga — tanpa flag --test, Node tetap mendeteksi module node:test dan memakai runner yang sama |
node --test | Menemukan dan menjalankan semua berkas test di folder kerja beserta subfolder-nya |
Cara kedua berguna saat kita sedang fokus pada satu berkas; hasilnya identik dengan pemanggilan melalui --test:
node dasar.test.mjsOutput yang Dihasilkan:
✔ tambah menjumlahkan dua angka
✔ bagi membagi angka pertama dengan angka kedua
ℹ tests 2
ℹ suites 0
ℹ pass 2
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0Cara ketiga adalah bentuk yang paling sering kita pakai sehari-hari. Tanpa argumen, test runner mencari semua berkas yang cocok dengan pola berikut: *.test.mjs, *.test.js, *.test.cjs, *-test.*, *_test.*, test-*, test.*, serta semua berkas JavaScript di dalam folder test/; daftar polanya tercantum pada dokumentasi test runner
. Kita akan melihat hasilnya utuh pada bagian terakhir chapter ini, setelah seluruh berkas contohnya selesai dibahas.
Mengelompokkan dengan describe dan it #
Ketika jumlah test bertambah, daftar datar menjadi sulit dipindai. Function describe() mengelompokkan test yang menguji fitur yang sama, dan di dalamnya kita boleh memakai it() — nama lain dari test() yang lazim dipakai berpasangan dengan describe:
import { describe, it, test } from 'node:test';
import assert from 'node:assert';
import { tambah, bagi } from './kalkulator.mjs';
describe('penjumlahan', () => {
it('menjumlahkan dua angka positif', () => {
assert.strictEqual(tambah(20, 22), 42);
});
it('menjumlahkan angka negatif', () => {
assert.strictEqual(tambah(-5, 3), -2);
});
});
describe('pembagian', () => {
it('membagi dua angka', () => {
assert.strictEqual(bagi(10, 4), 2.5);
});
it('menolak pembagi nol', () => {
assert.throws(() => bagi(5, 0), { message: 'pembagi tidak boleh nol' });
});
test('subtest memeriksa beberapa kasus sekaligus', async (t) => {
await t.test('10 dibagi 2', () => {
assert.strictEqual(bagi(10, 2), 5);
});
await t.test('9 dibagi 3', () => {
assert.strictEqual(bagi(9, 3), 3);
});
});
});node --test berkelompok.test.mjsOutput yang Dihasilkan:
▶ penjumlahan
✔ menjumlahkan dua angka positif
✔ menjumlahkan angka negatif
✔ penjumlahan
▶ pembagian
✔ membagi dua angka
✔ menolak pembagi nol
▶ subtest memeriksa beberapa kasus sekaligus
✔ 10 dibagi 2
✔ 9 dibagi 3
✔ subtest memeriksa beberapa kasus sekaligus
✔ pembagian
ℹ tests 7
ℹ suites 2
ℹ pass 7
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0Tanda ▶ menandai suite yang dibuka dan ditutup dengan nama describe-nya. Dua hal baru pada berkas ini perlu dibedah:
- Baris 5 dan 14:
describe(nama, callback)membentuk suite; setiapit()di dalamnya menjadi bagian dari nama lengkap test, misalnya pembagian > menolak pembagi nol. - Baris 23-29 (subtest): object
t— context yang diterima callback test — memiliki methodt.test()untuk mendaftarkan subtest. Subtest berguna ketika satu perilaku layak diperiksa lewat banyak pasangan masukan: setiap subtest dilaporkan terpisah sehingga kita tahu persis kasus mana yang gagal. - Kata kunci
asyncdanawait:t.test()mengembalikan promise, sehingga kita menunggu setiap subtest selesai sebelum mendaftarkan yang berikutnya — materi async dalam testing dibahas tuntas pada Chapter Menguji Kode Asynchronous.
Melewatkan Test dan Menandai Todo #
Dua penanda pada object opsi membantu mengelola suite yang sedang setengah jalan. Opsi skip mencegah runner menjalankan isi test sama sekali — isi tersebut bisa berisi assertion yang pasti gagal sekalipun. Opsi todo menandai pekerjaan yang belum dikerjakan tanpa memerlukan isi test:
import { test } from 'node:test';
import assert from 'node:assert';
test('kalkulasi dasar tetap berjalan', () => {
assert.strictEqual(1 + 1, 2);
});
test('kasus khusus angka negatif', { skip: 'menunggu perbaikan bug #12' }, () => {
assert.ok(false);
});
test('perilaku bilangan desimal', { todo: 'dilengkapi pada rilis berikutnya' });node --test penanda.test.mjsOutput yang Dihasilkan:
✔ kalkulasi dasar tetap berjalan
﹣ kasus khusus angka negatif # menunggu perbaikan bug #12
✔ perilaku bilangan desimal # dilengkapi pada rilis berikutnya
ℹ tests 3
ℹ suites 0
ℹ pass 1
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 1Simbol ﹣ menandai test yang dilewati beserta alasannya, sedangkan test bertanda todo ditampilkan dengan ✔ dan dicatat pada baris rangkuman todo. Kedua penanda ini didokumentasikan pada bagian skipping tests
dan todo tests
. Perhatikan bahwa assert.ok(false) pada test kedua tidak pernah dieksekusi — itulah bukti bahwa skip benar-benar mencegah isi test berjalan. Alasan yang tertulis (menunggu perbaikan bug #12) ikut tercetak sehingga rekan tim tahu mengapa test tersebut diam.
Menyaring Test yang Dijalankan #
Saat mendiagnosis satu kegagalan, kita tidak perlu menjalankan seluruh suite. Flag --test-name-pattern menerima regular expression dan hanya menjalankan test yang namanya cocok — termasuk seluruh isi suite yang namanya cocok:
node --test --test-name-pattern=pembagian berkelompok.test.mjsOutput yang Dihasilkan:
▶ pembagian
✔ membagi dua angka
✔ menolak pembagi nol
▶ subtest memeriksa beberapa kasus sekaligus
✔ 10 dibagi 2
✔ 9 dibagi 3
✔ subtest memeriksa beberapa kasus sekaligus
✔ pembagian
ℹ tests 5
ℹ suites 1
ℹ pass 5
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0Seluruh suite penjumlahan tidak muncul pada output karena namanya tidak memuat kata pembagian. Pola ini berlaku terhadap nama lengkap test, sehingga --test-name-pattern="menolak" juga akan menangkap test menolak pembagi nol di dalam suite mana pun.
Hook: Kode Sebelum dan Sesudah Test #
Kode persiapan yang sama sering dibutuhkan banyak test — menyiapkan data, membuka koneksi, lalu membersihkannya. Test runner menyediakan empat hook: before berjalan sekali sebelum seluruh suite, after sekali setelahnya, sementara beforeEach dan afterEach mengelilingi setiap test. Berkas berikut membuktikan urutannya dengan merekam jejak eksekusi:
import { test, describe, before, after, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert';
const jejak = [];
describe('kumpulan dengan hook', () => {
before(() => jejak.push('before'));
after(() => jejak.push('after'));
beforeEach(() => jejak.push('beforeEach'));
afterEach(() => jejak.push('afterEach'));
test('test pertama', () => jejak.push('test-1'));
test('test kedua', () => jejak.push('test-2'));
});
test('urutan eksekusi lengkap', () => {
assert.deepStrictEqual(jejak, [
'before',
'beforeEach', 'test-1', 'afterEach',
'beforeEach', 'test-2', 'afterEach',
'after',
]);
});node --test urutan-hook.test.mjsOutput yang Dihasilkan:
▶ kumpulan dengan hook
✔ test pertama
✔ test kedua
✔ kumpulan dengan hook
✔ urutan eksekusi lengkap
ℹ tests 3
ℹ suites 1
ℹ pass 3
ℹ fail 0
ℹ cancelled 0
ℹ skipped 0
ℹ todo 0Test terakhir di luar suite memeriksa isi jejak setelah seluruh suite selesai — dan lulus, yang berarti urutan sesungguhnya memang before, lalu pasangan beforeEach-test-afterEach untuk setiap test, lalu after tepat satu kali. Hook ini akan kembali dipakai pada Chapter Integration Test untuk HTTP Server untuk menyalakan dan mematikan server uji.
Menjalankan Seluruh Suite Sekaligus #
Sekarang keempat berkas contoh pada chapter ini telah lengkap, dan inilah waktunya melihat cara ketiga dari tabel di awal: node --test tanpa argumen menemukan semuanya secara otomatis.
node --testOutput yang Dihasilkan:
▶ penjumlahan
✔ menjumlahkan dua angka positif
✔ menjumlahkan angka negatif
✔ penjumlahan
▶ pembagian
✔ membagi dua angka
✔ menolak pembagi nol
▶ subtest memeriksa beberapa kasus sekaligus
✔ 10 dibagi 2
✔ 9 dibagi 3
✔ subtest memeriksa beberapa kasus sekaligus
✔ pembagian
✔ tambah menjumlahkan dua angka
✔ bagi membagi angka pertama dengan angka kedua
✔ kalkulasi dasar tetap berjalan
﹣ kasus khusus angka negatif # menunggu perbaikan bug #12
✔ perilaku bilangan desimal # dilengkapi pada rilis berikutnya
▶ kumpulan dengan hook
✔ test pertama
✔ test kedua
✔ kumpulan dengan hook
✔ urutan eksekusi lengkap
ℹ tests 15
ℹ suites 3
ℹ pass 13
ℹ fail 0
ℹ cancelled 0
ℹ skipped 1
ℹ todo 1Rangkuman melaporkan 15 test dari empat berkas: tiga belas lulus, satu dilewati, satu bertanda todo. Angka-angka inilah yang nantinya dipantau otomatis oleh pipeline CI — perintah yang sama berjalan identis di komputer lokal dan di server build, dan kode keluar bukan nol akan menghentikan pipeline.
Ke Mana Setelah Ini #
Kita kini dapat mendeklarasikan test, mengelompokkannya, menyaringnya, dan mengaturnya dengan hook memakai node:test semata. Satu perangkat dari contoh-contoh di atas masih sengaja ditunda pembahasannya: assert.strictEqual beserta saudara-saudaranya dari module node:assert. Chapter Assertion dengan node:assert membedah pilihan assertion tersebut satu per satu — termasuk jebakan klasik antara equal dan strictEqual.