Test runner bawaan node:test telah membawa kita dari assertion pertama sampai integration test tanpa satu pun dependency. Namun proyek JavaScript di luar sana juga hidup di atas runner lain, dan yang paling banyak dipakai belakangan ini adalah Vitest — framework testing yang dibangun di atas Vite dengan API yang kompatibel dengan Jest. Kabar baiknya, hampir semua kebiasaan dari Section ini terbawa langsung: pola Arrange, Act, Assert, nama test yang menjelaskan perilaku, dan isolasi antar test. Yang berubah terutama peralatannya: assertion bergaya expect(...).toBe(...) yang berantai, watch mode yang cepat, dan laporan kegagalan yang kaya detail. Pada chapter penutup Section ini kita mencicipi Vitest — menulis test pertama, membandingkan matchernya dengan node:assert, lalu menjalankannya dari terminal dengan berbagai reporter.

Test Pertama dengan Vitest #

Vitest sudah tersedia di lingkungan buku ini sehingga cukup dipanggil lewat npx; pada proyek kita sendiri, cara resminya adalah memasang sebagai devDependency dengan npm install -D vitest seperti pada panduan memulai Vitest . Berkas test pertama kita sengaja dibuat seminimal mungkin:

js
import { test, expect } from 'vitest';

function jumlah(a, b) { return a + b; }

test('menjumlahkan dua angka', () => {
  expect(jumlah(2, 3)).toBe(5);
});

test('menangani angka negatif', () => {
  expect(jumlah(-1, 1)).toBe(0);
});

Penamaan jumlah.vitest.test.mjs memuat dua penanda sekaligus: akhiran .test.mjs membuat berkas dikenali sebagai berkas test, sedangkan sisipan vitest di tengah menegaskan pemiliknya — ingat bahwa node --test juga mengenali pola *.test.mjs, jadi penanda ini menjaga kedua runner tidak saling merebut berkas yang sama. Mari kita bedah isinya:

  1. Baris 1: test dan expect diimpor dari package vitest — tanpa prefix node: karena ini package eksternal, bukan module bawaan.
  2. Baris 3: untuk contoh pertama, function yang diuji didefinisikan di berkas yang sama; pada contoh berikutnya kita kembali memisahkannya ke module sendiri.
  3. Baris 5-7: test(nama, callback) — bentuk yang sama persis dengan node:test, sehingga cara menyusun suite tidak perlu dipelajari ulang.
  4. Baris 6: expect(hasil).toBe(harapan) — inilah wajah khas Vitest: nilai nyata dibungkus expect(), lalu matcher .toBe() membandingkannya secara ketat seperti assert.strictEqual.

Saatnya menjalankannya. Subperintah run berarti sekali jalan tanpa watch mode:

bash
npx vitest run jumlah.vitest.test.mjs

Output yang Dihasilkan:

text

 RUN  v5.0.5

 ✓ jumlah.vitest.test.mjs (2 tests)

 Test Files  1 passed (1)
      Tests  2 passed (2)

Matcher expect untuk Object dan Error #

Untuk matcher yang lebih beragam, kita kembali ke pola yang sehat: kode yang diuji dipisah ke module sendiri. Module berikut membuat object pengguna dan menolak nama kosong:

js
export function buatPengguna(nama, peran) {
  if (!nama) {
    throw new Error('nama wajib diisi');
  }
  return { nama, peran };
}

Perhatikan perilaku baris 5: karena peran tidak wajib, pemanggil yang tidak mengirim argumen kedua membuat properti peran bernilai undefined — detail ini akan menjadi bahan uji yang menarik sebentar lagi. Berkas testnya:

js
import { describe, it, expect } from 'vitest';
import { buatPengguna } from './pengguna.mjs';

describe('buatPengguna', () => {
  it('membuat pengguna dengan nama dan peran', () => {
    expect(buatPengguna('Budi', 'admin')).toEqual({ nama: 'Budi', peran: 'admin' });
  });

  it('nama pengguna berupa teks', () => {
    expect(buatPengguna('Budi', 'admin').nama).toBe('Budi');
  });

  it('menolak nama kosong dengan error', () => {
    expect(() => buatPengguna('')).toThrow('nama wajib diisi');
  });
});

Tiga matcher berbeda bekerja di dalamnya:

  1. Baris 6 (toEqual): perbandingan mendalam — seluruh properti object dibandingkan berulang sampai daun. Pasangannya di node:assert adalah assert.deepStrictEqual.
  2. Baris 10 (toBe): perbandingan ketat untuk nilai primitif; untuk object, matcher ini memeriksa identitas referensi yang sama.
  3. Baris 14 (toThrow): function dibungkus arrow tanpa dipanggil, lalu matcher memeriksa bahwa pemanggilannya melempar error; cukup memberi potongan pesan ('nama wajib diisi') karena pencocokannya terhadap error.message.
bash
npx vitest run pengguna.vitest.test.mjs

Output yang Dihasilkan:

text

 RUN  v5.0.5

 ✓ pengguna.vitest.test.mjs (3 tests)

 Test Files  1 passed (1)
      Tests  3 passed (3)

Bagi kita yang datang dari node:test, pemetaan berikut merangkum perjalanan sejauh ini:

Pada node:testPadanan di Vitest
test(), describe(), it()Sama — nama dan bentuknya identik
beforeEach(), afterEach()Sama — hook bernama sama, termasuk before dan after
assert.strictEqual(x, 5)expect(x).toBe(5)
assert.deepStrictEqual(a, b)expect(a).toStrictEqual(b)
assert.throws(fn, { message })expect(fn).toThrow('pesan')

Daftar matcher lengkapnya jauh lebih panjang — toContain, toHaveLength, toBeGreaterThan, sampai toMatchSnapshot — dan semuanya terdokumentasi pada referensi API expect .

toEqual versus toStrictEqual #

Dua matcher mendalam di Vitest tidak setara, dan bedanya persis di titik peran yang undefined tadi. toEqual mengabaikan properti bernilai undefined selama perbandingan; toStrictEqual menolaknya karena struktur object dianggap berbeda. Berkas berikut menguji keduanya bersebelahan — dan test keduanya sengaja kita tulis untuk melihat kegagalannya:

js
import { it, expect } from 'vitest';
import { buatPengguna } from './pengguna.mjs';

it('toEqual menerima peran yang undefined', () => {
  expect(buatPengguna('Siti')).toEqual({ nama: 'Siti' });
});

it('toStrictEqual menolak peran yang undefined', () => {
  expect(buatPengguna('Siti')).toStrictEqual({ nama: 'Siti' });
});

Test pertama lulus: { nama: 'Siti', peran: undefined } dianggap cukup sama dengan { nama: 'Siti' } oleh toEqual. Test kedua gagal — dan justru di sinilah Vitest bersinar. Jalankan berkasnya:

bash
npx vitest run ketat.vitest.test.mjs
echo "kode keluar: $?"

Output yang Dihasilkan:

text

 RUN  v5.0.5

 ❯ ketat.vitest.test.mjs (2 tests | 1 failed)
   ✓ toEqual menerima peran yang undefined
   × toStrictEqual menolak peran yang undefined

⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯

 FAIL  ketat.vitest.test.mjs > toStrictEqual menolak peran yang undefined
AssertionError: expected { nama: 'Siti', peran: undefined } to strictly equal { nama: 'Siti' }

- Expected
+ Received

  {
    "nama": "Siti",
+   "peran": undefined,
  }

 ❯ ketat.vitest.test.mjs:9:32
      7|
      8| it('toStrictEqual menolak peran yang undefined', () => {
      9|   expect(buatPengguna('Siti')).toStrictEqual({ nama: 'Siti' });
       |                                ^
     10| });
     11|

⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)

kode keluar: 1

Mari kita baca laporan kegagalannya bagian demi bagian:

  1. Baris FAIL: nama berkas dan nama test yang gagal — setara dengan blok failing tests pada node --test, tetapi hadir di bagian atas laporan.
  2. AssertionError: expected { nama: 'Siti', peran: undefined } to strictly equal { nama: 'Siti' }: pesan kegagalan menyebut kedua nilai secara eksplisit, bukan sekadar “nilai tidak sama”.
  3. Blok - Expected / + Received: perbandingan ala diff — tanda - menandai nilai yang kita harapkan, tanda + nilai yang benar-benar diterima. Baris + "peran": undefined langsung menunjuk properti penyebabnya.
  4. Baris ❯ ketat.vitest.test.mjs:9:32 beserta cuplikan kode: lokasi persis assertion yang gagal lengkap dengan baris kode di sekitarnya — koordinat baris:kolom sama seperti yang kita kenal dari membaca stack trace.
  5. Rangkuman Tests 1 failed | 1 passed (2) dan kode keluar: 1: satu dari dua test gagal sehingga proses keluar dengan kode 1 — pipeline CI akan menahan penggabungan, sama seperti perilaku node --test.

Reporter yang Bisa Dipilih #

Reporter bawaan menyembunyikan nama test yang lulus — baris ✓ pengguna.vitest.test.mjs (3 tests) hanya merangkum. Reporter verbose menampilkan setiap test sebaris, berguna saat kita ingin memastikan seluruh nama test terbaca seperti spesifikasi:

bash
npx vitest run --reporter=verbose pengguna.vitest.test.mjs

Output yang Dihasilkan:

text

 RUN  v5.0.5

 ✓ pengguna.vitest.test.mjs > buatPengguna > membuat pengguna dengan nama dan peran
 ✓ pengguna.vitest.test.mjs > buatPengguna > nama pengguna berupa teks
 ✓ pengguna.vitest.test.mjs > buatPengguna > menolak nama kosong dengan error

 Test Files  1 passed (1)
      Tests  3 passed (3)

Nama lengkap setiap test kini tampak — termasuk nama suite buatPengguna sebagai awalan. Reporter lain membidik kebutuhan yang berbeda: dot memadatkan hasil jadi titik-titik untuk suite raksasa, junit menghasilkan XML yang dibaca alat CI, dan tap menghasilkan format Test Anything Protocol yang mudah diurai mesin:

bash
npx vitest run --reporter=tap pengguna.vitest.test.mjs

Output yang Dihasilkan:

text
TAP version 13
1..1
ok 1 - pengguna.vitest.test.mjs {
    1..1
    ok 1 - buatPengguna {
        1..3
        ok 1 - membuat pengguna dengan nama dan peran
        ok 2 - nama pengguna berupa teks
        ok 3 - menolak nama kosong dengan error
    }
}

Setiap baris ok memuat satu test; angka 1..3 menyatakan rentang rencana, dan sarang kurung kurawal menggambarkan struktur suite. Angka durasi per test (# time=...) aslinya tercetak — perintah di buku menyaringnya dengan alasan yang sama seperti sebelumnya. Daftar lengkap beserta cara menggabungkan beberapa reporter sekaligus ada pada dokumentasi CLI Vitest .

Mode Watch untuk Siklus Cepat #

Sepanjang chapter ini kita memakai vitest run karena buku butuh eksekusi sekali jalan. Saat mengembangkan, bentuk yang lebih nyaman justru tanpa run: npx vitest pada terminal interaktif masuk ke watch mode — runner menunggu, lalu menjalankan ulang test setiap kali berkas berubah, dan hanya test yang terdampak perubahan itulah yang dijalankan ulang. Di lingkungan tanpa terminal interaktif seperti pipeline CI, Vitest otomatis jatuh ke perilaku run sehingga perintah yang sama aman dipakai di kedua tempat.

Ke Mana Setelah Ini #

Section Testing resmi tuntas: kita bisa menulis test dengan node:test, menguji kode asynchronous, memalsukan dependensi lewat mocking, mengukur coverage, menyusun integration test untuk HTTP server, dan kini mengenal Vitest sebagai alternatif dari ekosistem. Namun test hanya memberi tahu ada yang salah — mencari tahu mengapa salah adalah pekerjaan lain. Section berikutnya membekali kita untuk itu, dimulai dari mengintip ke dalam proses yang sedang berjalan pada Chapter Debugging dengan node –inspect.