Mocking & Stubbing — Testing

Mengapa Perlu Mocking? Saat unit testing, kita ingin menguji satu unit secara terisolasi. Tapi bagaimana jika unit tersebut bergantung pada API, database, atau

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:

JenisTujuanKapan dipakai
DummyPlaceholder — tidak pernah dipakai, hanya mengisi parameter wajibSaat constructor butuh argumen tapi kode yang diuji tidak memakainya
StubMengganti return value — tidak peduli bagaimana dipanggilSaat butuh data tetap dari dependency (misal: fetchUser() → user fix)
SpyMengamati pemanggilan, tetap menjalankan implementasi asliSaat ingin verifikasi fungsi dipanggil tanpa mengubah behaviour
MockSet ekspektasi pemanggilan lalu verifikasi (strict)Saat pemanggilan ke dependency adalah hal yang diuji itu sendiri
FakeImplementasi kerja ringan (misal in-memory DB) pengganti yang asliSaat 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.

Butuh test double? Perlu verifikasi pemanggilan? ya tidak Butuh jalankan asli? ya: Spy / tidak: Mock Butuh logic kerja? ya: Fake / tidak: Stub Placeholder saja? Dummy
Pohon keputusan: pilih test double sesuai niat pengujian.

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

Yang akan kamu pelajari