Panduan Vitest Lanjutan dengan Claude Code
Rancang Vitest dengan Claude Code: tiruan, timer palsu, jsdom, cakupan, cuplikan, dan CI.
Masalah yang Diselesaikan Alur Vitest Ini
Meminta Claude Code “tambahkan pengujian Vitest” saja tidak cukup. Pengujian bisa lulus di lokal, tetapi gagal saat menyentuh waktu, DOM, API eksternal, atau CI. Panduan ini merapikan area berisiko menjadi satu alur praktis: tiruan untuk mengganti dependensi, timer palsu untuk mengendalikan jam, cakupan untuk melihat cabang yang belum diuji, jsdom untuk struktur DOM, cuplikan kecil untuk kontrak tampilan, dan perintah CI yang benar-benar selesai.
Pada 3 Juni 2026, saya memeriksa dokumentasi resmi Vitest: Getting Started, Mocking, Timers, Dates, Test Environment, Coverage, Snapshot, dan CLI. Dokumentasi Vitest 4 membedakan mode pantau dan vitest run; di CI, perbedaan ini mencegah pekerjaan menggantung.
Gunakan Claude Code sebagai pasangan perancang pengujian. Jelaskan batas yang ditirukan, apakah jam harus dibekukan, apakah jsdom cukup, dan perintah apa yang membuktikan hasilnya. Untuk alur terkait, baca strategi pengujian Claude Code, panduan tiruan API dengan MSW, dan pengujian E2E Playwright.
flowchart TD
A["Spesifikasi: sukses dan gagal"] --> B["Vitest config: node/jsdom/coverage"]
B --> C["Unit: logika murni dan batas API"]
B --> D["Waktu: timer palsu dan Date tetap"]
B --> E["DOM: jsdom dan cuplikan"]
C --> F["CI: vitest run --coverage"]
D --> F
E --> F
Mulai dari Konfigurasi Stabil
Pasang Vitest, penyedia cakupan V8, jsdom, dan TypeScript. Aplikasi Vite bisa berbagi konfigurasi, tetapi vitest.config.ts khusus membuat niat pengujian mudah dibaca oleh Claude Code dan pemeriksa kode.
npm install -D vitest @vitest/coverage-v8 jsdom typescript
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"coverage": "vitest run --coverage"
}
}
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
environment: "node",
globals: false,
restoreMocks: true,
coverage: {
provider: "v8",
reporter: ["text", "html"],
include: ["src/**/*.{ts,tsx}"],
exclude: ["src/**/*.d.ts", "src/**/*.test.{ts,tsx}", "src/test/**"],
thresholds: {
lines: 80,
functions: 80,
branches: 75,
statements: 80,
},
},
},
});
globals: false membuat asal describe dan expect terlihat jelas. restoreMocks: true mengurangi kebocoran tiruan, tetapi timer palsu dan DOM tetap harus dibersihkan sendiri.
Kasus 1: Menirukan Batas API
Pengujian unit tidak seharusnya memanggil API pesanan, pembayaran, atau pengguna yang nyata. Uji kontrak yang kamu miliki: jalur, isi permintaan, validasi input, dan perubahan galat.
// src/orders.ts
export type ApiClient = {
post<T>(path: string, body: unknown): Promise<T>;
};
export class OrderError extends Error {
constructor(message = "Order request failed") {
super(message);
this.name = "OrderError";
}
}
type OrderInput = {
sku: string;
quantity: number;
};
type OrderResponse = {
id: string;
status: "accepted" | "queued";
};
export async function createOrder(api: ApiClient, input: OrderInput) {
if (input.quantity < 1) {
throw new OrderError("Quantity must be at least 1");
}
try {
return await api.post<OrderResponse>("/orders", input);
} catch {
throw new OrderError("Order API failed");
}
}
// src/orders.test.ts
import { describe, expect, it, vi } from "vitest";
import { createOrder, type ApiClient, OrderError } from "./orders";
describe("createOrder", () => {
it("posts the order payload to the API", async () => {
const api: ApiClient = {
post: vi.fn().mockResolvedValue({ id: "ord_1", status: "accepted" }),
};
await expect(createOrder(api, { sku: "book-1", quantity: 2 })).resolves.toEqual({
id: "ord_1",
status: "accepted",
});
expect(api.post).toHaveBeenCalledWith("/orders", { sku: "book-1", quantity: 2 });
});
it("rejects invalid quantity before calling the API", async () => {
const api: ApiClient = { post: vi.fn() };
await expect(createOrder(api, { sku: "book-1", quantity: 0 })).rejects.toBeInstanceOf(
OrderError,
);
expect(api.post).not.toHaveBeenCalled();
});
it("wraps transport errors in a domain error", async () => {
const api: ApiClient = {
post: vi.fn().mockRejectedValue(new Error("ECONNRESET")),
};
await expect(createOrder(api, { sku: "book-1", quantity: 1 })).rejects.toThrow(
"Order API failed",
);
});
});
Gaya injeksi dependensi ini sering lebih mudah dibaca daripada menukar seluruh modul. vi.mock() tetap berguna, tetapi Vitest mengangkatnya sebelum import; urutan awal yang salah dapat membingungkan pemula dan hasil buatan AI.
Kasus 2: Membekukan Waktu dengan Timer Palsu
Masa uji coba, percobaan ulang, notifikasi, dan debounce menjadi rapuh jika menunggu waktu nyata. Vitest dapat mengendalikan setTimeout, setInterval, dan tanggal sistem.
// src/trial.ts
const DAY_MS = 24 * 60 * 60 * 1000;
export function getTrialEndsAt(days = 7) {
return new Date(Date.now() + days * DAY_MS).toISOString();
}
export function scheduleTrialReminder(send: () => void, days = 7) {
return setTimeout(send, days * DAY_MS);
}
// src/trial.test.ts
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { getTrialEndsAt, scheduleTrialReminder } from "./trial";
describe("trial reminder", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.setSystemTime(new Date("2026-06-03T00:00:00.000Z"));
});
afterEach(() => {
vi.useRealTimers();
});
it("calculates the trial end date from the fixed clock", () => {
expect(getTrialEndsAt()).toBe("2026-06-10T00:00:00.000Z");
});
it("runs the reminder after the configured number of days", () => {
const send = vi.fn();
const timer = scheduleTrialReminder(send, 3);
vi.advanceTimersByTime(3 * 24 * 60 * 60 * 1000 - 1);
expect(send).not.toHaveBeenCalled();
vi.advanceTimersByTime(1);
expect(send).toHaveBeenCalledTimes(1);
clearTimeout(timer);
});
});
Kesalahan paling umum adalah lupa vi.useRealTimers(). Jam palsu dari satu berkas bisa membuat berkas lain gagal sesekali. Jika ada Promise, gunakan await. Batas tanggal dan zona waktu dibahas di panduan tanggal dan waktu Claude Code.
Kasus 3: Menjaga DOM dengan jsdom dan Cuplikan
jsdom meniru API DOM di dalam Node. Ini cocok untuk struktur, teks, dan atribut aksesibilitas. Ini bukan pengganti peramban nyata untuk layout, fokus, Canvas, atau regresi visual.
// src/notice.ts
export function renderNotice(target: HTMLElement, message: string) {
target.innerHTML = "";
const notice = document.createElement("p");
notice.setAttribute("role", "status");
notice.dataset.testid = "notice";
notice.textContent = message;
target.append(notice);
return notice;
}
// src/notice.test.ts
// @vitest-environment jsdom
import { afterEach, describe, expect, it } from "vitest";
import { renderNotice } from "./notice";
afterEach(() => {
document.body.innerHTML = "";
});
describe("renderNotice", () => {
it("renders an accessible status message", () => {
document.body.innerHTML = '<div id="app"></div>';
const target = document.querySelector<HTMLDivElement>("#app");
if (!target) throw new Error("missing #app");
const notice = renderNotice(target, "Tersimpan");
expect(notice.getAttribute("role")).toBe("status");
expect(notice.textContent).toBe("Tersimpan");
expect({
html: document.body.innerHTML,
text: notice.textContent,
}).toMatchInlineSnapshot(`
{
"html": "<div id=\\"app\\"><p role=\\"status\\" data-testid=\\"notice\\">Tersimpan</p></div>",
"text": "Tersimpan",
}
`);
});
});
Cuplikan harus kecil. Atribut penting diperiksa langsung dengan expect; cuplikan hanya menyimpan struktur ringkas yang tidak boleh berubah tanpa alasan. Perilaku peramban nyata sebaiknya diuji dengan Playwright.
Cakupan dan CI sebagai Gerbang Kualitas
Cakupan bukan untuk mengejar angka, melainkan untuk menemukan cabang yang belum diuji. Vitest mendokumentasikan penyedia V8 dan Istanbul, dengan V8 sebagai bawaan. Tetapkan coverage.include; jika tidak, berkas baru yang tidak pernah diimport oleh pengujian bisa hilang dari laporan.
# .github/workflows/vitest.yml
name: vitest
on:
pull_request:
push:
branches: [main]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm run test:run
- run: npm run coverage
Di CI, gunakan vitest run. Perintah vitest saja bisa masuk mode pantau. Desain alur yang lebih lengkap ada di panduan CI/CD Claude Code.
Prompt Praktis untuk Claude Code
Tambahkan pengujian Vitest untuk src/orders.ts.
Uji hanya createOrder.
Tirukan API eksternal dengan vi.fn(); jangan lakukan panggilan HTTP nyata.
Sertakan kasus sukses, input tidak valid, dan kegagalan transport.
Jangan gunakan timer palsu atau jsdom kecuali kode membutuhkannya.
Setelah mengedit, laporkan perintah npm run test:run yang diharapkan dan risiko tersisa.
Prompt ini memberikan ruang lingkup, batas tiruan, kasus gagal wajib, dan perintah bukti. Masukkan aturan serupa ke praktik terbaik CLAUDE.md agar sesi berikutnya tidak berhenti di jalur sukses saja.
Kegagalan yang Sering Terjadi
| Kegagalan | Gejala | Perbaikan |
|---|---|---|
| Tiruan tidak dipulihkan | Jumlah panggilan atau implementasi palsu bocor | Gunakan restoreMocks, vi.clearAllMocks(), atau vi.restoreAllMocks() sesuai tujuan |
| Timer palsu tidak dipulihkan | Pengujian waktu gagal di berkas lain | Panggil vi.useRealTimers() di afterEach |
| jsdom dianggap peramban nyata | CSS, layout, gambar, atau Canvas berbeda | Vitest untuk kontrak DOM, Playwright untuk perilaku peramban |
| Cuplikan terlalu besar | Tinjauan penuh gangguan | Simpan hanya struktur kecil |
coverage.include tidak ada | Berkas tanpa tes tidak terlihat | Sertakan src/**/*.{ts,tsx} secara jelas |
| Asinkron tidak ditunggu | Hasil positif palsu | Gunakan await expect(promise).resolves atau rejects |
| CI berjalan dalam mode pantau | Pekerjaan tidak selesai | Gunakan vitest run atau vitest related --run |
Jika ingin menyesuaikan alur ini ke repository tim, ClaudeCodeLab menyediakan halaman pelatihan berbahasa Inggris dan template praktis untuk standar pengujian, prompt tinjauan, dan gerbang CI.
Hasil Verifikasi
Hasil akhirnya adalah dasar Vitest dengan tiga kasus yang bisa disalin: batas API, waktu tetap, dan render jsdom dengan cuplikan kecil. Sebelum menerbitkan, saya memeriksa dokumen resmi Vitest, tautan internal, tautan eksternal, pagar kode, updatedDate, pengaturan cakupan, dan penggunaan vitest run di CI.
PDF gratis: cheatsheet Claude Code
Masukkan email dan unduh satu halaman berisi command, kebiasaan review, dan workflow aman.
Kami menjaga datamu dan tidak mengirim spam.
Tentang penulis
Masa
Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.
Artikel terkait
Permission receipt Claude Code: mencatat scope, bukti, dan rollback
Pola permission receipt untuk Claude Code: aksi yang diizinkan, batas approval, command verifikasi, rollback, dan cek CTA revenue.
Agent Harness Aman untuk Claude Code dan Codex: Permission, Verifikasi, dan Rollback
Rancang Agent Harness praktis untuk Claude Code dan Codex dengan policy, plan, verification, dan recovery layer.
Subagent Claude Code: panduan praktis untuk delegasi artikel dan kode
Panduan subagent Claude Code untuk membagi pekerjaan artikel dan kode: aturan delegasi, prompt, risiko, dan checklist.