Javascript Testing Unit Integration Mocking Async Tests
Selesai membaca dokumentasi ini?
Kembali ke rujukan utama JavaScript untuk melanjutkan topik lainnya.
Kembali ke rujukan utama JavaScript untuk melanjutkan topik lainnya.
Testing di JavaScript adalah cara untuk memverifikasi bahwa kode berjalan sesuai perilaku yang diharapkan, bukan hanya sekadar lolos saat dijalankan sekali. Di kerja harian, testing dipakai untuk menangkap regresi, menjaga kontrak perilaku, dan memberi sinyal cepat saat refactor merusak hal yang tidak kelihatan dari hasil akhir saja.
Topik ini biasanya mencakup empat hal utama:
Kalau kode mulai tumbuh, testing bukan lagi opsi tambahan. Testing jadi alat untuk menjaga perubahan tetap aman, terutama saat ada beberapa layer yang saling memengaruhi.
Testing paling berguna ketika kamu ingin jawaban cepat untuk pertanyaan seperti:
Kalau diringkas, testing dipakai saat:
Testing biasanya paling terasa manfaatnya saat bug muncul berulang. Begitu bug bisa ditulis sebagai test, dia berhenti jadi cerita lisan dan berubah jadi spesifikasi yang bisa dijalankan ulang.
Secara praktis, komposisi test yang sehat sering digambarkan sebagai piramida: banyak unit test, lebih sedikit integration test, dan lebih sedikit lagi end-to-end test.
Bukan berarti integration test itu buruk atau unit test selalu cukup. Yang sehat adalah proporsinya: logika inti banyak diuji di level kecil, sedangkan alur penting di level integrasi mendapat cakupan yang cukup.
Unit testing menguji satu unit perilaku kecil dengan dependency seminimal mungkin. Di JavaScript, unit test sering dipakai untuk fungsi pure, helper, validator, reducer, formatter, atau logic kecil yang hasilnya deterministik.
// formatPrice.js
export function formatPrice(amount) {
return new Intl.NumberFormat('id-ID', {
style: 'currency',
currency: 'IDR',
}).format(amount);
}import { formatPrice } from './formatPrice.js';
describe('formatPrice', () => {
it('memformat angka ke rupiah', () => {
expect(formatPrice(15000)).toContain('Rp');
});
});Integration testing menguji bagaimana beberapa bagian bekerja bersama. Di JavaScript, ini bisa berarti komponen + state + event handler, service + fetch wrapper + parser, atau route handler + validation + response helper.
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import { UserSearch } from './UserSearch';
global.fetch = vi.fn();
describe('UserSearch', () => {
it
| Aspek | Unit Test | Integration Test |
|---|---|---|
| Fokus | satu unit kecil | beberapa bagian yang bekerja bersama |
| Dependency | diminimalkan | sebagian nyata, sebagian boleh dimock |
| Kecepatan | paling cepat | lebih lambat |
| Diagnosis saat gagal | paling mudah | sedikit lebih kompleks |
| Nilai utama | validasi logika kecil | validasi koneksi antarbagian |
Kalau kamu ragu, tanyakan satu hal sederhana: apakah test ini memverifikasi logika internal, atau kerja sama antarbagian? Kalau yang diuji adalah kerja sama, itu sudah masuk wilayah integration test.
Mocking adalah teknik mengganti dependency asli dengan versi kontrol. Tujuannya bukan menipu test, tapi membuat test fokus pada perilaku yang ingin divalidasi.
Mock dipakai saat dependency:
Contoh dependency yang sering di-mock:
fetchDate.now()Math.random()localStorageMocking fetch adalah kebutuhan yang sangat umum karena network call tidak cocok dijadikan dependency nyata di unit test.
import { getUser } from './getUser';
global.fetch = vi.fn();
describe('getUser', () => {
beforeEach(() => {
fetch.mockReset();
});
it
fetch.mockResolvedValueOnce(...) untuk response sukses bertahapfetch.mockRejectedValueOnce(...) untuk network failureok: false untuk simulasi HTTP errorjson: async () => ... untuk body JSONfetch.mockResolvedValueOnce({
ok: false,
status: 500,
statusText: 'Internal Server Error',
});Kalau code kamu memeriksa response.ok, test harus meniru shape response yang sama. Jangan cukup mengembalikan object kosong kalau code produksi butuh properti tertentu.
Mocking module dipakai ketika kamu ingin mengganti satu modul dependency dengan versi palsu dalam test. Ini umum untuk API client, helper yang memanggil network, atau module yang side-effect-nya tidak ingin jalan.
vi.mock('./apiClient', () => ({
apiClient: {
get: vi.fn(),
post: vi.fn(),
},
}));import { apiClient } from './apiClient';
import { loadProfile } from './loadProfile';
describe('loadProfile', () => {
it('memakai apiClient yang dimock', async () => {
apiClient.get.mockResolvedValueOnce
jest.mock('./apiClient', () => ({
apiClient: {
get: jest.fn(),
},
}));Struktur test yang bagus membuat test lebih mudah dipindai, lebih mudah dirawat, dan lebih gampang dipahami saat gagal.
describe('sum', () => {
it('menjumlahkan dua angka', () => {
// Arrange
const a = 2;
const b = 3;
// Act
const result = sum(a, b);
Pola ini membantu test tetap fokus. Kalau satu test mulai berisi banyak aksi, biasanya dia perlu dipecah.
Nama test sebaiknya menyebut perilaku, bukan detail implementasi.
Contoh yang bagus:
menampilkan error saat request gagalmengembalikan user aktif dari daftarmenolak password yang terlalu pendekContoh yang kurang bagus:
harus memanggil function Xtes bagian 1worksAsync test sering gagal bukan karena logikanya salah, tapi karena alur timing-nya tidak diikuti dengan benar.
Kalau kamu tidak await, assertion bisa berjalan sebelum operasi async selesai. Hasilnya test bisa:
awaitit('mengambil data', () => {
const result = fetchData();
expect(result).toBe(...);
});Kalau fetchData() mengembalikan Promise, test di atas salah karena assertion dilakukan sebelum hasil tersedia.
Perbaikan:
it('mengambil data', async () => {
const result = await fetchData();
expect(result).toBe(...);
});Kalau UI update terjadi setelah state berubah, gunakan await waitFor(...) atau query async yang sesuai. Jangan pakai setTimeout sembarangan hanya untuk “menunggu”.
Jika function memang seharusnya melempar, test harus menuliskannya dengan jelas.
await expect(loadProfile()).rejects.toThrow('Gagal');Kadang callback async selesai setelah test sudah dianggap selesai. Gejalanya: test hijau padahal callback tidak pernah diverifikasi.
Kalau mock tidak di-reset, hasil test sebelumnya bisa memengaruhi test berikutnya.
Biasakan:
beforeEach(() => {
vi.clearAllMocks();
vi.resetAllMocks();
});atau padanan di Jest sesuai kebutuhan.
Jangan menguji “berapa lama” sesuatu selesai kecuali memang itu yang penting. Uji hasil akhirnya, bukan implementasi delay-nya.
Di JavaScript, pola test bisa dijalankan dengan beberapa tool:
Yang penting bukan merek alatnya, tapi konsistensi struktur dan cara membaca hasil test.
fetch dan module yang berhubungan dengan dependency luardescribe untuk mengelompokkan behaviorBiasanya ada tiga kemungkinan:
Dalam kasus seperti itu, test sering lebih mudah ditulis setelah code dirapikan, bukan sebaliknya.
awaitEvent Loop, Promise, Microtask, dan async/awaitModules JavaScript: import/export, Default vs Named Export, Dynamic Import, dan Struktur File yang SehatFetch API, Async Data Flow, JSON Parsing, dan Error Handling DasarError Handling di JavaScript: try/catch, throw, Custom Error, dan Debugging Mindset