Mengapa Perlu Mocking?
Saat unit testing, kita ingin menguji satu unit secara terisolasi. Tapi bagaimana jika unit tersebut bergantung pada API, database, atau modul lain? Jawabannya: mocking.
Mocking memungkinkan kita mengganti dependensi nyata dengan versi palsu yang perilakunya bisa kita kontrol.
vi.fn() — Mock Function
Membuat fungsi tiruan yang bisa melacak pemanggilan dan mengontrol return value:
import { vi, describe, it, expect } from "vitest";
const kalkulasi = vi.fn();
// Mengatur return value
kalkulasi.mockReturnValue(42);
expect(kalkulasi()).toBe(42);
// Mengatur implementasi
kalkulasi.mockImplementation((a, b) => a + b);
expect(kalkulasi(2, 3)).toBe(5);
// Cek apakah dipanggil
expect(kalkulasi).toHaveBeenCalled();
expect(kalkulasi).toHaveBeenCalledWith(2, 3);
expect(kalkulasi).toHaveBeenCalledTimes(2);
vi.mock() — Mock Module
Mengganti seluruh modul dengan versi mock. Sangat berguna untuk mengisolasi dari HTTP client atau service lain:
import { vi, describe, it, expect } from "vitest";
import axios from "axios";
import { ambilUser } from "./userService";
vi.mock("axios");
describe("ambilUser", () => {
it("mengembalikan data user dari API", async () => {
const mockData = { id: 1, nama: "Budi" };
axios.get.mockResolvedValue({ data: mockData });
const result = await ambilUser(1);
expect(axios.get).toHaveBeenCalledWith("/api/users/1");
expect(result).toEqual(mockData);
});
});
vi.spyOn() — Spy tanpa Replace
Memantau pemanggilan method tanpa mengganti implementasi aslinya:
const objek = {
hitung(x) {
return x * 2;
},
};
const spy = vi.spyOn(objek, "hitung");
objek.hitung(5); // tetap menjalankan implementasi asli
expect(spy).toHaveBeenCalledWith(5);
expect(spy).toHaveReturnedWith(10);
spy.mockRestore(); // kembalikan ke implementasi asli
Test Doubles Matrix
Istilah "mock" sering dipakai bebas, padahal sebenarnya ada 5 jenis test double yang berbeda tujuannya. Tabel berikut merangkumnya:
| Jenis | Tujuan | Kapan dipakai |
|---|---|---|
| Dummy | Placeholder — tidak pernah dipakai, hanya mengisi parameter wajib | Saat constructor butuh argumen tapi kode yang diuji tidak memakainya |
| Stub | Mengganti return value — tidak peduli bagaimana dipanggil | Saat butuh data tetap dari dependency (misal: fetchUser() → user fix) |
| Spy | Mengamati pemanggilan, tetap menjalankan implementasi asli | Saat ingin verifikasi fungsi dipanggil tanpa mengubah behaviour |
| Mock | Set ekspektasi pemanggilan lalu verifikasi (strict) | Saat pemanggilan ke dependency adalah hal yang diuji itu sendiri |
| Fake | Implementasi kerja ringan (misal in-memory DB) pengganti yang asli | Saat butuh perilaku realistis tapi lebih cepat/terkontrol |
Aturan praktis: pilih double paling ringan yang cukup. Spy > Mock saat hanya butuh observasi. Fake > Mock saat logikanya kompleks dan dipakai banyak test.
Membersihkan Mock
afterEach(() => {
vi.restoreAllMocks(); // reset semua mock ke implementasi asli
});
// Atau secara individual:
// mockFn.mockClear() — reset riwayat panggilan
// mockFn.mockReset() — clear + hapus return value
// mockFn.mockRestore() — reset + kembalikan implementasi asli
Best Practices
- Jangan over-mock — mock hanya dependensi eksternal (API, database, file system)
- Bersihkan mock — gunakan
afterEach(() => vi.restoreAllMocks()) - Prefer spyOn — jika hanya perlu memantau, jangan replace seluruh fungsi
- Mock di level modul — untuk isolasi yang lebih baik