Advanced (Diperbarui: 2/6/2026)

Strategi Testing dengan Claude Code: Vitest, Testing Library, Playwright, dan CI

Rancang unit, integration, E2E, dan CI testing dengan Claude Code tanpa membuat test rapuh.

Strategi Testing dengan Claude Code: Vitest, Testing Library, Playwright, dan CI

Strategi testing bukan sekadar menulis lebih banyak test. Strategi testing menentukan perilaku mana yang dilindungi unit test cepat, batas mana yang perlu integration test, dan alur bisnis mana yang layak dijaga dengan E2E.

Kalau Anda hanya meminta Claude Code “tambahkan test”, hasilnya bisa berupa assertion dangkal, selector CSS yang rapuh, atau test yang terlalu menempel ke implementasi saat ini. Prompt yang lebih baik menyebut layer test, perilaku yang ingin dijaga, command yang harus dijalankan, dan batas file yang boleh diubah.

Panduan ini dicek terhadap dokumentasi resmi Claude Code common workflows, Vitest coverage, Testing Library queries, serta Playwright untuk locators, assertions, dan CI.

Mulai dari piramida testing

flowchart TB
  E2E["E2E: signup, checkout, CTA revenue"]
  INT["Integration: API, DB, form, component"]
  UNIT["Unit: calculation, validation, permission"]
  E2E --> INT --> UNIT
LayerPatokanYang dilindungiTools
Unit60-70%logika murni, validasi, permissionVitest
Integration20-30%component, API, batas DBVitest + Testing Library
E2E5-10%signup, checkout, jalur revenuePlaywright
CI gatesetiap PRlint, types, tests, reportsGitHub Actions

Logika harga cocok untuk unit test. Tombol checkout cocok untuk integration test. Perjalanan dari CTA artikel ke halaman produk cocok untuk E2E.

Setup minimal

npm i -D vitest @vitest/coverage-v8 jsdom \
  @testing-library/react @testing-library/jest-dom @testing-library/user-event \
  @playwright/test
npx playwright install --with-deps
{
  "scripts": {
    "test": "vitest --run",
    "test:watch": "vitest",
    "test:coverage": "vitest --run --coverage",
    "test:e2e": "playwright test"
  }
}
// vitest.config.ts
import { defineConfig } from "vitest/config";

export default defineConfig({
  test: {
    environment: "jsdom",
    setupFiles: ["./test/setup.ts"],
    coverage: {
      provider: "v8",
      reporter: ["text", "html"],
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
        statements: 80,
      },
    },
  },
});
// test/setup.ts
import "@testing-library/jest-dom/vitest";

Vitest mendokumentasikan provider coverage v8 dan istanbul. Untuk project Node atau Chromium, v8 biasanya pilihan yang paling praktis.

Contoh 1: Unit test untuk logika harga

// src/lib/pricing.ts
export type PriceInput = {
  unitPrice: number;
  quantity: number;
  discountRate?: number;
  taxRate?: number;
};

export function calculateTotal({
  unitPrice,
  quantity,
  discountRate = 0,
  taxRate = 0.1,
}: PriceInput): number {
  if (!Number.isInteger(quantity) || quantity < 0) {
    throw new Error("quantity must be a non-negative integer");
  }
  if (unitPrice < 0) throw new Error("unitPrice must be non-negative");
  if (discountRate < 0 || discountRate > 1) {
    throw new Error("discountRate must be between 0 and 1");
  }
  const discounted = unitPrice * quantity * (1 - discountRate);
  return Math.round(discounted * (1 + taxRate));
}
// src/lib/pricing.test.ts
import { describe, expect, it } from "vitest";
import { calculateTotal } from "./pricing";

describe("calculateTotal", () => {
  it("calculates a tax-included total", () => {
    expect(calculateTotal({ unitPrice: 1000, quantity: 2 })).toBe(2200);
  });

  it("applies discount before tax", () => {
    expect(
      calculateTotal({ unitPrice: 1000, quantity: 2, discountRate: 0.2 })
    ).toBe(1760);
  });

  it("allows zero quantity", () => {
    expect(calculateTotal({ unitPrice: 1000, quantity: 0 })).toBe(0);
  });

  it("rejects invalid inputs", () => {
    expect(() => calculateTotal({ unitPrice: 1000, quantity: -1 })).toThrow(
      "quantity must be a non-negative integer"
    );
  });
});

Kesalahan umum adalah mengejar coverage 80% dengan happy path saja. Untuk harga, kasus pentingnya adalah quantity nol, diskon penuh, rate invalid, dan pembulatan.

Contoh 2: CTA dengan Testing Library

Testing Library merekomendasikan query yang mirip dengan cara pengguna menemukan elemen. Karena itu getByRole dan getByLabelText lebih stabil daripada class CSS.

// src/components/CheckoutButton.tsx
import { useState } from "react";

type Props = {
  stock: number;
  onCheckout: () => Promise<void>;
};

export function CheckoutButton({ stock, onCheckout }: Props) {
  const [submitting, setSubmitting] = useState(false);
  const soldOut = stock <= 0;

  async function handleClick() {
    setSubmitting(true);
    try {
      await onCheckout();
    } finally {
      setSubmitting(false);
    }
  }

  return (
    <button
      type="button"
      disabled={soldOut || submitting}
      aria-busy={submitting}
      onClick={handleClick}
    >
      {soldOut ? "Sold out" : submitting ? "Processing..." : "Buy now"}
    </button>
  );
}
// src/components/CheckoutButton.test.tsx
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { describe, expect, it, vi } from "vitest";
import { CheckoutButton } from "./CheckoutButton";

describe("CheckoutButton", () => {
  it("calls checkout when in stock", async () => {
    const user = userEvent.setup();
    const onCheckout = vi.fn().mockResolvedValue(undefined);

    render(<CheckoutButton stock={3} onCheckout={onCheckout} />);
    await user.click(screen.getByRole("button", { name: "Buy now" }));

    expect(onCheckout).toHaveBeenCalledTimes(1);
  });

  it("prevents checkout when sold out", async () => {
    const user = userEvent.setup();
    const onCheckout = vi.fn().mockResolvedValue(undefined);

    render(<CheckoutButton stock={0} onCheckout={onCheckout} />);
    const button = screen.getByRole("button", { name: "Sold out" });

    expect(button).toBeDisabled();
    await user.click(button);
    expect(onCheckout).not.toHaveBeenCalled();
  });
});

CTA yang berhubungan dengan revenue tidak sebaiknya diuji lewat .primary-button atau snapshot saja. Test harus memastikan role, teks, dan perilakunya benar.

Contoh 3: Playwright E2E yang kecil

// playwright.config.ts
import { defineConfig, devices } from "@playwright/test";

const baseURL =
  process.env.PLAYWRIGHT_TEST_BASE_URL ?? "http://127.0.0.1:5173";

export default defineConfig({
  testDir: "./e2e",
  retries: process.env.CI ? 2 : 0,
  use: { baseURL, trace: "on-first-retry" },
  projects: [{ name: "chromium", use: { ...devices["Desktop Chrome"] } }],
  webServer: process.env.PLAYWRIGHT_TEST_BASE_URL
    ? undefined
    : {
        command: "npm run dev -- --host 127.0.0.1",
        url: baseURL,
        reuseExistingServer: !process.env.CI,
      },
});
// e2e/article-cta.spec.ts
import { expect, test } from "@playwright/test";

test("reader can move from article CTA to products", async ({ page }) => {
  await page.goto("/id/blog/claude-code-testing-strategies");

  await page.getByRole("link", { name: /products|templates|produk/i }).click();

  await expect(page).toHaveURL(/\/products\/?$/);
  await expect(page.getByRole("heading", { name: /products|produk/i })).toBeVisible();
});

Assertion web-first di Playwright melakukan retry otomatis. Gunakan await expect(locator).toBeVisible() daripada waktu tunggu tetap. Hindari path CSS yang dalam dan nth().

Contoh 4: CI gate

# .github/workflows/test.yml
name: Test

on:
  pull_request:
  push:
    branches: [main]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v6
        with:
          node-version: 22
          cache: npm
      - run: npm ci
      - run: npm run test:coverage
      - run: npx playwright install --with-deps
      - run: npm run test:e2e
        env:
          CI: "true"
      - uses: actions/upload-artifact@v5
        if: ${{ !cancelled() }}
        with:
          name: playwright-report
          path: playwright-report/
          retention-days: 30

Jika memakai Claude Code GitHub Actions, ikuti action GA saat ini, yaitu anthropics/claude-code-action@v1. Jangan menyalin contoh lama @beta tanpa mengecek dokumentasi.

Template prompt untuk Claude Code

Tambahkan unit test Vitest untuk src/lib/pricing.ts.
Cover success cases, boundary values, dan invalid inputs.
Jangan ubah implementasi kecuali Anda lebih dulu mencatat dugaan bug.
Jalankan npm run test -- pricing dan rangkum hasilnya.
Tambahkan test Testing Library untuk src/components/CheckoutButton.tsx.
Gunakan getByRole dan userEvent.setup().
Jangan gunakan selector CSS, snapshot sebagai assertion utama, atau detail implementasi.
Cover state in-stock, sold-out, dan submitting.
Tambahkan satu test Playwright E2E untuk flow CTA artikel ke products.
Hindari waitForTimeout, selector CSS dalam, dan nth().
Gunakan web-first assertions dan fokus pada revenue path.
Baca kegagalan CI terbaru.
Klasifikasikan sebagai lint, typecheck, unit, e2e, atau environment.
Perbaiki root cause dengan diff terkecil.
Jangan skip test dan jangan hanya menambah timeout.

Jebakan umum

Jangan mock semuanya. Payment provider dan email delivery boleh dimock, tetapi harga, permission, dan persistensi penting harus diuji di layer yang tepat.

Jangan gunakan E2E untuk mengunci semua detail visual. E2E harus melindungi signup, checkout, contact, dan klik produk.

Jangan anggap coverage sebagai bukti kualitas. Coverage 80% masih bisa melewatkan branch yang melakukan charge, delete, atau publish.

Jangan beri Claude Code happy path saja. Tambahkan larangan: tidak ada selector rapuh, tidak ada skip, tidak snapshot-only, dan tidak coupling ke detail implementasi.

CTA

Testing juga melindungi jalur revenue. Mulai dari Claude Code cheatsheet, ubah aturan review berulang menjadi products and templates, lalu gunakan Claude Code training jika tim perlu menyatukan CI, aturan review, dan kepemilikan test. Untuk lanjut, baca TDD with Claude Code, CI/CD setup, dan debugging techniques.

Hasil saat dicoba

Masa mencoba pola ini pada CTA artikel dan flow menuju halaman produk di ClaudeCodeLab. Hasil terbaik bukan dari menambah banyak E2E, tetapi dari menaruh pricing logic di unit test, perilaku CTA di Testing Library, dan satu test Playwright untuk flow artikel-ke-produk. Saat Claude Code diminta mencatat failure mode lebih dulu, test yang di-skip dan selector CSS rapuh berkurang. Risiko tersisa ada pada payment eksternal dan script iklan, yang masih perlu bukti CI ditambah pengecekan manual singkat sebelum publish.

#Claude Code #testing #test strategy #Vitest #Playwright
Gratis

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.

Masa

Tentang penulis

Masa

Engineer yang berfokus pada workflow Claude Code praktis dan adopsi tim.