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
- Setup Proyek dan Test Pertama Anda
- Menguasai Mock dan Code Asinkron
- Strategi Lanjutan untuk Tes yang Robust
- Testing untuk CI, Capacitor, dan Electron Apps
- Pertanyaan Umum tentang Unit Testing JavaScript
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.

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
describedantestatauit - 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.

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.
- Arrange input dan setup yang diperlukan.
- Act dengan memanggil fungsi.
- 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.

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.

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.