Menulis kode saja tidak cukup — kamu juga harus memastikan kode tersebut bekerja dengan benar dan tidak rusak saat diubah di kemudian hari. Di sinilah automated testing berperan. Pada artikel ini, kamu akan belajar menulis test untuk proyek JavaScript dan TypeScript menggunakan Vitest, sebuah test runner modern yang super cepat, minim konfigurasi, dan kompatibel dengan ekosistem Vite. Kita akan membangun semuanya dari nol, lengkap dengan contoh kode yang bisa langsung kamu jalankan.

Kenapa Testing Itu Penting?

Bayangkan kamu menulis sebuah fungsi, mengujinya manual lewat console.log, lalu lanjut mengembangkan fitur lain. Beberapa minggu kemudian, kamu mengubah satu baris kode dan — tanpa kamu sadari — fitur lama jadi rusak. Tanpa test, kamu baru tahu bug-nya setelah dilaporkan pengguna. Automated test berperan sebagai jaring pengaman: ia otomatis memeriksa bahwa perilaku kode tetap sesuai ekspektasi setiap kali ada perubahan, sehingga kamu bisa refactor dengan percaya diri.

Konsep testing di sini mirip dengan yang kamu pelajari di artikel Belajar Testing Python dengan Pytest. Jika kamu masih baru dengan JavaScript, sebaiknya pahami dulu dasar-dasar JavaScript: variabel, fungsi, dan DOM sebelum lanjut.

Mengenal Vitest

Vitest adalah test runner modern yang dibangun di atas Vite. Ia mengadopsi API Jest yang sudah populer — seperti describe, it, dan expect — tetapi jauh lebih cepat dan hampir tanpa konfigurasi. Vitest mendukung JavaScript dan TypeScript secara bawaan, punya watch mode, code coverage, mocking, hingga snapshot testing. Karena sintaksnya mirip Jest, kamu tidak akan kesulitan berpindah dari satu alat ke alat lainnya.

Persiapan: Install Vitest

Kita mulai dengan membuat folder proyek baru dan menginstal Vitest sebagai devDependency (dependency yang hanya dipakai saat pengembangan, bukan di produksi). Pastikan Node.js sudah terpasang di komputermu.

bash
mkdir belajar-vitest && cd belajar-vitest
npm init -y
npm install -D vitest

Agar bisa memakai sintaks import/export (ES Modules), buka file package.json lalu tambahkan baris "type": "module" di dalamnya. Contoh isi package.json-mu:

json
{
  "name": "belajar-vitest",
  "type": "module",
  "scripts": {
    "test": "vitest run",
    "test:watch": "vitest",
    "coverage": "vitest run --coverage"
  },
  "devDependencies": {
    "vitest": "^2.1.0"
  }
}

Buat file math.js berisi fungsi sederhana yang akan kita test, lalu buat file math.test.js untuk test-nya. Konvensi penamaan Vitest adalah *.test.js atau *.spec.js.

javascript
// math.js
export function tambah(a, b) {
  return a + b;
}

export function bagi(a, b) {
  if (b === 0) {
    throw new Error('Tidak bisa membagi dengan nol');
  }
  return a / b;
}
javascript
// math.test.js
import { describe, it, expect } from 'vitest';
import { tambah, bagi } from './math.js';

describe('fungsi tambah', () => {
  it('menjumlahkan dua angka positif', () => {
    expect(tambah(2, 3)).toBe(5);
  });

  it('menjumlahkan angka negatif', () => {
    expect(tambah(-1, -1)).toBe(-2);
  });
});

describe('fungsi bagi', () => {
  it('membagi dua angka', () => {
    expect(bagi(10, 2)).toBe(5);
  });

  it('melempar error saat pembagi nol', () => {
    expect(() => bagi(10, 0)).toThrow('Tidak bisa membagi dengan nol');
  });
});

Jalankan test dengan perintah berikut di terminal:

bash
npx vitest run

Tips: gunakan nama test yang deskriptif (contoh: "menjumlahkan dua angka positif") alih-alih "test1". Saat test gagal, pesan errornya akan langsung menjelaskan perilaku apa yang rusak, sehingga debugging jauh lebih cepat.

Memahami Matchers (Assertion)

Fungsi expect(nilai) mengembalikan objek assertion, lalu kamu memanggil sebuah matcher seperti .toBe() untuk membandingkan nilai. Berikut beberapa matcher yang paling sering dipakai:

javascript
import { describe, it, expect } from 'vitest';

describe('contoh matcher', () => {
  it('membandingkan berbagai nilai', () => {
    expect(1 + 1).toBe(2);               // sama persis (===)
    expect({ a: 1 }).toEqual({ a: 1 });  // deep equality untuk objek
    expect('bengkelkode').toContain('kode');
    expect([1, 2, 3]).toHaveLength(3);
    expect(null).toBeNull();
    expect(undefined).toBeUndefined();
    expect(0.1 + 0.2).toBeCloseTo(0.3);  // untuk angka desimal
  });
});

Perhatikan perbedaan .toBe() dan .toEqual(): .toBe() membandingkan referensi (cocok untuk primitif), sedangkan .toEqual() membandingkan isi secara mendalam (cocok untuk objek dan array).

Testing Kode Asynchronous

Di dunia nyata, banyak kode JavaScript bersifat asynchronous — misalnya memanggil API lewat fetch. Kalau kamu belum paham konsep async/await dan Promise, baca dulu panduan lengkap Async/Await di JavaScript. Vitest memudahkan testing kode async dengan await dan vi.fn() untuk mocking.

javascript
// fetchUser.js
export async function fetchUser(id) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  if (!res.ok) {
    throw new Error('Gagal mengambil data user');
  }
  return res.json();
}
javascript
// fetchUser.test.js
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { fetchUser } from './fetchUser.js';

describe('fetchUser', () => {
  beforeEach(() => {
    global.fetch = vi.fn();
  });

  it('mengembalikan data user', async () => {
    const mockUser = { id: 1, name: 'Leanne Graham' };
    global.fetch.mockResolvedValue({
      ok: true,
      json: async () => mockUser,
    });

    const user = await fetchUser(1);
    expect(user.name).toBe('Leanne Graham');
    expect(global.fetch).toHaveBeenCalledWith(
      'https://jsonplaceholder.typicode.com/users/1'
    );
  });

  it('melempar error saat response gagal', async () => {
    global.fetch.mockResolvedValue({ ok: false });

    await expect(fetchUser(999)).rejects.toThrow('Gagal mengambil data user');
  });
});

Mocking dan Spy

Mocking adalah teknik mengganti fungsi atau modul asli dengan versi tiruan saat test, supaya test berjalan cepat, terisolasi, dan tidak bergantung pada layanan eksternal. Pada contoh di atas, kita sudah melakukan mocking terhadap global.fetch menggunakan vi.fn(). Kamu juga bisa memakai vi.spyOn() untuk "memata-matai" metode sebuah objek tanpa mengubah implementasinya, misalnya memastikan sebuah fungsi dipanggil berapa kali.

Testing REST API dengan Supertest

Selain unit test, kamu juga butuh integration test untuk memastikan endpoint API bekerja dari request hingga response. Untuk aplikasi Express, kita gunakan library supertest. Kalau kamu belum familiar dengan Express, pelajari dulu di tutorial REST API dengan Node.js dan Express.

bash
npm install express
npm install -D supertest
javascript
// app.js
import express from 'express';

const app = express();
app.use(express.json());

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.post('/api/users', (req, res) => {
  const { name } = req.body;
  if (!name) {
    return res.status(400).json({ error: 'name wajib diisi' });
  }
  res.status(201).json({ id: 1, name });
});

export default app;
javascript
// app.test.js
import { describe, it, expect } from 'vitest';
import request from 'supertest';
import app from './app.js';

describe('REST API', () => {
  it('GET /api/health mengembalikan status ok', async () => {
    const res = await request(app).get('/api/health');
    expect(res.status).toBe(200);
    expect(res.body).toEqual({ status: 'ok' });
  });

  it('POST /api/users membuat user baru', async () => {
    const res = await request(app).post('/api/users').send({ name: 'Budi' });
    expect(res.status).toBe(201);
    expect(res.body.name).toBe('Budi');
  });

  it('POST /api/users menolak request tanpa name', async () => {
    const res = await request(app).post('/api/users').send({});
    expect(res.status).toBe(400);
  });
});

Watch Mode dan Code Coverage

Saat mengembangkan, jalankan npm run test:watch agar Vitest otomatis menjalankan ulang test setiap kali kamu menyimpan file. Sedangkan untuk melihat seberapa banyak kode yang tertutup test, jalankan perintah coverage berikut (install dulu plugin coverage-nya):

bash
npm install -D @vitest/coverage-v8
npm run coverage

Peringatan: angka coverage 100% tidak berarti kode kamu bebas bug. Coverage hanya menunjukkan seberapa banyak baris, fungsi, dan cabang yang dijalankan test — bukan kualitas assertion-nya. Fokuslah pada test yang bermakna, bukan sekadar mengejar angka hijau.

Kesimpulan

Selamat! Kamu sudah belajar dasar-dasar testing JavaScript dengan Vitest: mulai dari instalasi, menulis test pertama, memahami matcher, menguji kode asynchronous, melakukan mocking, hingga integration test untuk REST API Express. Dengan kebiasaan menulis test sejak awal, kode kamu akan lebih andal dan mudah dirawat. Kalau kamu ingin memperdalam JavaScript lebih lanjut, lanjutkan membaca dasar-dasar JavaScript atau eksplorasi artikel lainnya di BengkelKode. Selamat mengetes!