Langsung ke konten utama

Unit Test JavaScript: Panduan Lengkap 2026

Kuasai unit test JavaScript dengan panduan 2026 kami. Mencakup Jest, Mocha, setup, mocking, CI, dan tips untuk aplikasi Capacitor & Electron.

Ilustrasi blog Capgo 'Unit Tests JavaScript: Panduan Komprehensif 2026'

Anda kemungkinan besar sedang berada dalam salah satu dari dua situasi ini. Entah proyek JavaScript Anda hampir tidak punya test dan setiap refactor terasa berisiko, atau Anda sudah punya test tapi setengahnya lambat, rapuh, dan sulit dipercaya.

Hal ini makin parah di Capacitor dan Electron aplikasi. Satu fitur sederhana bisa menyentuh logika bisnis bersama, API browser, plugin native, file lokal, IPC, dan layanan remote dalam satu alur. Jika Anda menguji bagian-bagian itu dengan cara yang salah, suite Anda akan menjadi labirin dependensi palsu. Jika diuji dengan benar, Anda mendapat umpan balik cepat tentang logika yang rusak.

Unit test JavaScript yang baik tidak dimulai dengan sintaks matcher yang cerdas. Dimulai dari batasan yang disiplin: uji logika murni secara langsung, isolasi efek samping, dan hindari menulis test yang runtuh begitu Anda mengubah nama fungsi internal.

Table of Contents

Memilih framework pengujian JavaScript Anda

Proyek JavaScript profesional membutuhkan test runner yang benar. Skrip ad hoc dan pemeriksaan manual di console tidak scalable ketika beberapa engineer menyentuh codebase yang sama. Anda membutuhkan test discovery, assertions, async handling, mocks, dan cara untuk menjalankan semuanya secara konsisten di local development dan CI.

Panduan saat ini terus konvergen ke sejumlah kecil opsi mainstream. Jest, Mocha, dan Jasmine sering disorot sebagai framework utama, dengan Jest sering dipilih karena menyediakan struktur test, assertions, mocking, dan dukungan async dalam satu package, seperti yang ditunjukkan di lab pengujian JavaScript Pluralsight.

Tabel perbandingan yang menampilkan framework pengujian JavaScript populer, termasuk Jest, Mocha, Cypress, dan Playwright.

Mengapa framework bukan opsional

Kesalahan pertama yang dilakukan tim adalah menganggap unit tests sebagai aktivitas sampingan. Hal ini biasanya menyebabkan penamaan file yang tidak konsisten, assertions kustom yang tidak ada yang ingat, dan helper yang hanya dipahami satu orang.

Framework memberikan bahasa bersama:

  • Struktur test dengan describe dan test atau it
  • Assertions dengan matcher yang mudah dibaca
  • Hooks untuk setup dan teardown
  • Dukungan async untuk promises dan timers
  • Alat mocking untuk dependensi eksternal

Jika tim Anda juga butuh gambaran lebih luas tentang otomasi pengujian di luar unit test, Capgo punya ringkasan berguna tentang pengujian otomatis dalam workflow pengiriman aplikasi.

Jest vs Mocha sekilas

Jest dan Mocha mewakili dua filosofi yang berbeda.

Jest adalah opsi all-in-one. Sudah dilengkapi sebagian besar kebutuhan tim sejak hari pertama.
Mocha lebih modular. Anda mendapat runner dan diharapkan merakit sendiri sisa stack-nya.

Fitur Jest Mocha
Kompleksitas setup Lebih rendah untuk sebagian besar tim Lebih tinggi karena biasanya Anda menambahkan pustaka assertion dan mocking
Assertions Bawaan Biasanya dipasangkan dengan pustaka lain
Mocking Built-in Biasanya dipasangkan dengan library lain
Pengujian asinkron Built-in dan langsung Didukung, tapi lebih bergantung pada setup di sekitarnya
Workflow coverage Umumnya terintegrasi ke toolchain yang sama Sering kali lebih terfragmentasi
Pilihan terbaik Proyek baru, tim yang menginginkan konsistensi Stack legacy, tim yang menginginkan kontrol modular

Aturan praktis: Jika tim Anda harus bertanya library assertion dan mocking mana yang cocok dengan runner, kemungkinan besar Anda ingin menggunakan Jest.

Yang saya rekomendasikan untuk kebanyakan tim

Untuk sebagian besar proyek modern, saya akan memilih Jest kecuali jika codebase sudah punya alasan kuat untuk tetap memakai Mocha. Rekomendasi ini makin kuat kalau aplikasinya menyertakan Capacitor atau Electron, karena proyek seperti itu sudah punya cukup banyak bagian yang bergerak. Mengurangi tumpukan testing tool cepat terasa manfaatnya.

Mocha masih masuk akal untuk layanan Node.js lama atau codebase berumur panjang yang ekosistemnya sudah stabil. Tapi bagi engineer tingkat menengah yang menyiapkan suite solid dari nol, Jest biasanya lebih banyak mengurangi hambatan daripada menambahkannya.

Satu catatan penting soal scope. Cypress dan Playwright adalah tool yang bagus, tapi keduanya menyelesaikan masalah yang berbeda. Keduanya lebih cocok untuk pengecekan level browser dan end-to-end, bukan untuk fast inner loop tempat unit test JavaScript seharusnya berada.

Setup Proyek dan Test Pertama Anda

Setup testing yang rapi seharusnya membosankan. Kalau menambahkan test pertama terasa rumit, suite-nya kemungkinan besar tidak akan bertahan sehat.

Seorang pria berkacamata sedang mengerjakan proyek pemrograman di laptop di atas meja kayu.

Setup Jest yang sederhana

Mulailah dengan proyek JavaScript yang sudah memiliki package.json. Lalu tambahkan Jest sebagai dev dependency dan siapkan script test.

{
  "scripts": {
    "test": "jest"
  }
}

Itu sudah cukup untuk banyak proyek. Anda bisa menambahkan konfigurasi lain nanti jika sistem modul, transpilasi, atau struktur monorepo memerlukannya.

Jika Anda sedang membangun aplikasi Capacitor secara lokal dan ingin merapikan dev environment sebelum menambahkan test pada shared logic, panduan Capgo tentang menyiapkan local environment Capacitor adalah pendamping yang praktis.

Tulis test sebelum code

Pola test-first bukan sekadar preferensi pribadi. Panduan JavaScript dari U.S. Consumer Financial Protection Bureau secara eksplisit menyarankan menulis test terlebih dahulu, menyusun test dengan describe dan it, dan menyusun pengecekan seputar expect(...) assertion dalam Panduan unit testing JavaScript.

Hal ini penting karena pendekatan test-first mengubah cara Anda mendesain code. Fungsi cenderung menjadi lebih kecil, dependensi lebih terlihat, dan efek samping tidak lagi bocor ke logika yang seharusnya tetap murni.

Berikut contoh minimalnya:

// math.js
function addTax(amount, rate) {
  return amount + amount * rate;
}

module.exports = { addTax };
// math.test.js
const { addTax } = require('./math');

describe('addTax', () => {
  it('returns the amount with the tax applied', () => {
    expect(addTax(100, 0.2)).toBe(120);
  });
});

Gunakan Arrange Act Assert setiap kali

Panduan Arrange, Act, Assert pola ini menjaga agar test tetap mudah dibaca, bahkan saat kompleksitasnya meningkat.

  1. Arrange input dan setup yang diperlukan.
  2. Act dengan memanggil fungsi.
  3. Assert pada hasilnya.

Diterapkan pada helper validasi:

function isSupportedPlatform(platform) {
  return ['ios', 'android', 'web', 'desktop'].includes(platform);
}

describe('isSupportedPlatform', () => {
  it('returns true for ios', () => {
    // Arrange
    const platform = 'ios';

    // Act
    const result = isSupportedPlatform(platform);

    // Assert
    expect(result).toBe(true);
  });
});

Test kecil lebih awet. Sebuah test sebaiknya menjawab satu pertanyaan, bukan menceritakan seluruh workflow.

Untuk proyek Capacitor dan Electron, disiplin ini lebih penting karena logika murni Anda sering berdampingan dengan integrasi native atau desktop code. Jaga agar aturan bisnis tetap bisa di-test tanpa platform runtime, sehingga test pertama Anda tidak akan menjadi test terakhir yang berguna.

Menguasai Mock dan Code Asinkron

Sebagian besar bug dalam code aplikasi tidak berasal dari penjumlahan dua angka. Mereka berasal dari code yang mencapai keluar dari dirinya sendiri: request jaringan, file, plugin API, timer, channel IPC, lapisan storage.

Di sinilah mocking membantu. Ini memberi Anda kontrol atas boundary sehingga test dapat fokus pada pengambilan keputusan code Anda.

Diagram papan tulis yang mengilustrasikan arsitektur microservices dengan API, data store, layanan eksternal, dan alur data berbasis event.

Mock di boundary-nya, bukan semuanya

Panduan pengujian yang dapat dipelihara menekankan cakupan satu perilaku dan satu asersi kuat per test, dan juga memperingatkan bahwa penggunaan mock berlebihan membuat test rapuh dan sangat terikat pada detail implementasi, seperti dirangkum dalam artikel TestRail tentang unit test yang dapat dipelihara.

Peringatan itu sangat penting di JavaScript. Tim sering mulai dengan mocking setiap modul yang diimpor, lalu berakhir menguji apakah fungsi memanggil fungsi lain dalam urutan “benar”, alih-alih menguji perilaku nyata.

Target buruk untuk test yang banyak mock:

  • apakah helper A memanggil helper B
  • apakah service C memanggil serializer D
  • apakah fungsi privat internal dijalankan dua kali

Target yang lebih baik:

  • apa yang dikembalikan oleh fungsi
  • apakah kegagalan dependensi ditangani dengan benar
  • apakah data ditransformasi ke bentuk yang diharapkan

Pola yang lebih baik untuk Capacitor dan Electron code

Untuk aplikasi mobile dan desktop, saya lebih suka menggunakan lapisan wrapper di sekitar API native atau platform. Kemudian unit test akan mem-mock wrapper tersebut, bukan platformnya sendiri.

Struktur contoh:

// cameraGateway.js
async function getPhoto(cameraPlugin) {
  return cameraPlugin.getPhoto();
}

module.exports = { getPhoto };
// profilePhotoService.js
async function loadProfilePhoto(cameraGateway) {
  const photo = await cameraGateway.getPhoto();
  return { path: photo.path, ready: true };
}

module.exports = { loadProfilePhoto };
// profilePhotoService.test.js
const { loadProfilePhoto } = require('./profilePhotoService');

test('returns mapped photo data', async () => {
  const fakeCameraGateway = {
    getPhoto: jest.fn().mockResolvedValue({ path: '/tmp/pic.jpg' })
  };

  const result = await loadProfilePhoto(fakeCameraGateway);

  expect(result).toEqual({ path: '/tmp/pic.jpg', ready: true });
});

Pola itu juga berlaku untuk Electron. Bungkus ipcRenderer, akses file, atau integrasi shell di balik adapter tipis. Unit test akan menguji lapisan service, bukan runtime secara langsung.

Untuk tim yang menguji logika rilis dan jalur update pada aplikasi Capacitor, Capgo memiliki panduan relevan tentang menguji update OTA Capacitor dengan skenario mock.

Panduan singkat ini membantu jika tim Anda masih menyeragamkan gaya test async:

Menguji alur async tanpa flakiness

Gunakan async/await dalam test ketika code yang diuji mengembalikan promise. Ini lebih jelas daripada pola berbasis callback dan lebih mudah di-debug.

async function fetchProfile(api) {
  const response = await api.getUser();
  return response.name;
}

test('returns the user name from the API response', async () => {
  const api = {
    getUser: jest.fn().mockResolvedValue({ name: 'Ava' })
  };

  const result = await fetchProfile(api);

  expect(result).toBe('Ava');
});

Uji juga jalur kegagalan:

test('throws when the API request fails', async () => {
  const api = {
    getUser: jest.fn().mockRejectedValue(new Error('network failed'))
  };

  await expect(fetchProfile(api)).rejects.toThrow('network failed');
});

Uji jalur yang mulus dan jalur yang berantakan. Di produksi, jalur berantakan biasanya yang paling diingat pengguna.

Strategi Lanjutan untuk Tes yang Robust

Suite test menjadi berguna jika tetap berguna setelah code berubah. Itu lebih sulit daripada menulis tumpukan test yang lolos.

Diagram yang mengilustrasikan strategi membangun perangkat lunak yang robust melalui cakupan test yang komprehensif dan suite test yang mudah dipelihara.

Gunakan pembagian tes sebagai budget

Satu panduan praktis merekomendasikan 70/20/10 terbagi di unit, integration, dan end-to-end tests, dengan unit tests memberikan umpan balik tercepat dan kegagalan paling stabil. Panduan yang sama mengatakan bahwa suite unit penuh sebaiknya selesai dalam kurang dari 10 detik, dan pre-commit checks sebaiknya tetap kurang dari 5 detik, menurut data ini Panduan pengujian OpenReplay.

Saya memperlakukannya sebagai alat perencanaan anggaran, bukan dogma. Jika sebagian besar upaya Anda terbuang untuk end-to-end tests, tim Anda akan menunggu terlalu lama untuk umpan balik. Jika semuanya hanya unit tests, Anda akan melewatkan batas sistem yang sebenarnya.

Untuk aplikasi Capacitor atau Electron, keseimbangan yang sehat biasanya terlihat seperti ini:

  • Unit test untuk logika harga, aturan izin, serialisasi, kelayakan update, feature flags, dan transformasi state
  • Uji integrasi untuk storage adapter, plugin wrapper, dan kontrak IPC
  • Tes E2E untuk beberapa alur kritis seperti login, alur pembelian, sinkronisasi, atau prompt pembaruan

Coverage itu senter, bukan target

Laporan coverage berguna saat membantu Anda menemukan cabang yang belum diuji dalam logika penting. Laporan ini menjadi merugikan ketika tim mengejar persentase coverage demi persentase itu sendiri.

Validator login dengan tes edge-case yang cermat memberikan nilai lebih daripada file yang tercakup penuh dengan asersi sepele. Hal ini terutama berlaku untuk code yang banyak input seperti formulir, parser, logika tanggal, dan pemeriksaan izin. Jika tim Anda sedang memperketat kualitas di sekitar UI yang banyak validasi, panduan ini tentang menguasai validasi formulir frontend adalah pelengkap yang baik untuk strategi pengujian tingkat unit.

Tes behavior-first bertahan saat refactor

Suite yang andal harus memungkinkan Anda merfaktor internal tanpa menulis ulang setengah dari tes. Cara termudah untuk mencapainya adalah dengan mengasertikan perilaku yang dapat diamati bukan detail implementasi.

Kasus penggunaan yang tetap relevan:

  • Boundary conditions seperti input kosong, nilai mirip null, tipe tidak valid, dan string yang terlalu besar
  • Hasil domain misalnya “mengembalikan denied jika izin tidak ada”
  • Transisi state seperti “menandai update sebagai pending setelah metadata download divalidasi”

Use case yang sering jadi usang:

  • memeriksa pemanggilan helper internal
  • memastikan urutan private method
  • mocking setiap layer dalam call chain

Bagi tim app yang membangun proses rilis disiplin, artikel Capgo tentang quality assurance app berguna karena menghubungkan kerja testing dengan release pipeline yang lebih luas.

Testing untuk CI, Capacitor, dan Electron Apps

Test yang cuma jalan di mesin satu developer bukanlah safety net. Itu cuma kebiasaan lokal.

CI mengubah kerja unit test JavaScript menjadi infrastruktur tim. Setiap push, pull request, atau release branch bisa menjalankan command yang sama dengan ekspektasi yang sama. Konsistensi ini makin krusial untuk proyek Capacitor dan Electron, di mana environment drift memicu kegagalan yang halus.

Jadikan CI sebagai jalur eksekusi default

Minimal, CI Anda harus menginstal dependensi dan menjalankan unit suite pada setiap perubahan. Sebisa mungkin, gunakan perintah yang sama dengan pengembangan lokal.

Alur kerja GitHub Actions dasar bisa sesederhana ini:

name: test

on: [push, pull_request]

jobs:
  unit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test

Itu sudah cukup untuk mendeteksi import yang rusak, assertion yang gagal, dan asumsi platform yang tidak disengaja sebelum masuk ke main.

Untuk tim mobile yang merilis lewat pipeline otomatis, Capgo punya panduan praktis untuk setup CI/CD untuk aplikasi Capacitor.

Testing interaksi plugin Capacitor

Cara yang salah dalam unit test Capacitor code adalah menarik native plugin langsung ke setiap service. Ini membuat test suite Anda terikat ke platform bridge.

Pattern yang lebih baik adalah abstraksi tipis:

// deviceStorage.js
async function saveFile(filesystem, path, data) {
  return filesystem.writeFile({ path, data });
}

module.exports = { saveFile };
// draftService.js
async function persistDraft(storage, draft) {
  await storage.save('draft.json', JSON.stringify(draft));
  return { saved: true };
}

module.exports = { persistDraft };
// draftService.test.js
const { persistDraft } = require('./draftService');

test('persists a serialized draft', async () => {
  const storage = {
    save: jest.fn().mockResolvedValue(undefined)
  };

  const result = await persistDraft(storage, { title: 'Hello' });

  expect(result).toEqual({ saved: true });
});

Ide yang sama berlaku untuk akses kamera, biometric prompt, registrasi push-token, dan status jaringan. Letakkan pemanggilan plugin di dalam adapter. Test logika aplikasi terhadap interface yang Anda kontrol.

Testing Electron main renderer dan IPC code

Aplikasi Electron punya dua titik sambung penting: proses utama code dan proses renderer code. Jangan blur mereka dalam test.

Setup yang andal biasanya memisahkan:

  • Unit test renderer untuk view models, state, formatting, dan logika bisnis sisi UI
  • Unit test main process untuk menu, operasi file, dan keputusan siklus hidup aplikasi
  • Tes kontrak IPC untuk bentuk pesan dan respons yang diharapkan

Contoh wrapper IPC:

// ipcGateway.js
function sendSettings(ipcRenderer, payload) {
  ipcRenderer.send('settings:update', payload);
}

module.exports = { sendSettings };
// ipcGateway.test.js
const { sendSettings } = require('./ipcGateway');

test('sends settings update over ipc', () => {
  const ipcRenderer = { send: jest.fn() };

  sendSettings(ipcRenderer, { theme: 'dark' });

  expect(ipcRenderer.send).toHaveBeenCalledWith('settings:update', { theme: 'dark' });
});

Jika nanti Anda mengubah implementasi internal dari satu helper ke helper lain, tes ini tetap berlaku karena memverifikasi perilaku yang penting. Itulah standar yang Anda inginkan di desktop dan mobile code.

Pertanyaan yang Sering Diajukan tentang Unit Testing JavaScript

Apa perbedaan antara unit test, integration test, dan E2E test

Sebuah unit test memeriksa satu bagian kecil logika secara terisolasi. Sebuah integration test memeriksa apakah beberapa komponen atau layanan bekerja sama dengan benar. Sebuah test end-to-end menguji alur pengguna melalui aplikasi yang sedang berjalan.

Gunakan unit test untuk keyakinan cepat pada aturan bisnis. Gunakan integration test untuk titik sambung seperti storage, plugin wrapper, dan IPC. Gunakan E2E test secara terbatas untuk alur kerja yang akan sangat berdampak jika rusak.

Apakah kita harus mengejar cakupan penuh

Tidak. Cakupan penuh bisa mendorong tim ke arah test bernilai rendah.

Cakupan berguna ketika mengungkap code berisiko yang belum diuji. Tidak berguna jika engineer menambahkan asersi dangkal hanya untuk memuaskan dashboard. Jika suite Anda rapuh, cakupan lebih banyak tidak akan menyelamatkannya.

Bagaimana cara menambahkan test ke basis kode yang sudah ada

Mulai dari tempat perubahan sudah terjadi. Jangan bekukan tim dan mengumumkan penulisan ulang besar-besaran strategi test.

Urutan praktisnya seperti ini:

  • Lindungi code aktif terlebih dahulu dengan menambahkan test ke modul yang Anda sentuh selama pengembangan fitur atau perbaikan bug
  • Ekstrak logika murni dari file yang sulit diuji agar aturan bisnis bisa diuji tanpa noise framework atau runtime
  • Tambahkan seam wrapper sekitar native plugins, network clients, filesystem calls, dan Electron IPC
  • Tolak pola yang rapuh saat memperkenalkan mocks. Panduan dari Praktik terbaik pengujian JavaScript sangat berguna di sini karena menyoroti masalah over-mocking yang sering terlewat dan tes rapuh yang menyertainya

Tujuannya bukan kelengkapan instan. Melainkan perbaikan bertahap di area di mana regresi paling merugikan tim.


Jika tim Anda merilis Capacitor atau Electron aplikasi dan membutuhkan proses rilis yang lebih rapi untuk perubahan JavaScript, Capgo adalah salah satu opsi yang layak dipertimbangkan. Platform ini menyediakan Live Update untuk aplikasi CapacitorJS dan Electron, dilengkapi kontrol rollout dan observability, sehingga tim dapat menggabungkan unit testing yang solid dengan jalur pengiriman perubahan web bundle yang lebih aman tanpa harus menunggu review store untuk setiap perbaikan.

Live updates untuk aplikasi Capacitor

Saat bug web-layer sudah live, kirim perbaikannya lewat Capgo alih-alih menunggu berhari-hari untuk persetujuan app store. Pengguna mendapat update di background, sementara perubahan native tetap mengikuti jalur review normal.

Terbaru dari Blog kami

Capgo memberikan insight terbaik yang Anda butuhkan untuk membuat aplikasi mobile yang benar-benar profesional.