Testing Svelte — Svelte

Testing Svelte komponen menggunakan @testing-library/svelte dan Vitest. Setup npm install -D @testing-library/svelte vitest jsdom Test Komponen import {…

Test adalah kode yang memakai komponenmu seperti pembeli memakainya, lalu memeriksa hasilnya secara otomatis. Bayangin Bu Sri punya pembeli percobaan. Tiap pagi sebelum warung buka, dia memesan hal yang sama, menekan tombol yang sama, dan mencocokkan struknya. Kalau angkanya meleset, dia langsung lapor, sebelum pembeli sungguhan kena. Vitest adalah yang mengirim pembeli percobaan dan menulis laporannya, Testing Library adalah tangan dan matanya, dan jsdom adalah warung tiruan tempat dia mencoba.

🧪 Tebakan cepat: tes "dua klik +1 jadi Count: 2" dijalankan pada Counter yang tombolnya menulis count = 1, bukan count += 1. Tes itu lulus atau gagal, dan apa yang dilaporkan tentang halamannya? Jawabannya ada di bagian "Saat tes gagal".

Pasang Vitest dan Testing Library

Di proyek SvelteKit Warung Bu Sri:

npm install -D vitest @testing-library/svelte @testing-library/jest-dom jsdom
// vite.config.js (potongan)
import { svelteTesting } from "@testing-library/svelte/vite"

export default defineConfig({
  plugins: [sveltekit(), svelteTesting()],
  test: {
    environment: "jsdom",
    setupFiles: ["./vitest-setup.js"],
  },
})

Tes pertama: render dan getByRole

Komponennya sama dengan latihan:

<!-- src/lib/Counter.svelte -->
<script>
  let count = 0
</script>

<p>Count: {count}</p>
<button on:click={() => (count += 1)}>+1</button>
// src/lib/Counter.test.js
import { render, screen, fireEvent } from "@testing-library/svelte"
import { describe, it, expect } from "vitest"
import Counter from "./Counter.svelte"

describe("Counter", () => {
  it("mulai dari Count: 0", () => {
    render(Counter)
    expect(screen.getByText("Count: 0")).toBeInTheDocument()
  })

  it("dua klik +1 jadi Count: 2", async () => {
    render(Counter)
    const tombol = screen.getByRole("button", { name: "+1" })
    await fireEvent.click(tombol)
    await fireEvent.click(tombol)
    expect(screen.getByText("Count: 2")).toBeInTheDocument()
  })
})

Dijalankan di proyek SvelteKit 3 sungguhan dengan npx vitest run --reporter=verbose (--reporter=verbose mencetak nama tiap tes; ini potongannya):

 ✓ src/lib/Counter.test.js > Counter > mulai dari Count: 0 26ms
 ✓ src/lib/Counter.test.js > Counter > dua klik +1 jadi Count: 2 59ms
      Tests  2 passed (2)

Store dan action: tanpa render

Store dan action adalah JavaScript biasa, jadi dites langsung tanpa komponen:

// src/lib/keranjang.test.js
import { get } from "svelte/store"
import { describe, it, expect, beforeEach } from "vitest"
import { keranjang } from "./keranjang.js"

describe("keranjang", () => {
  beforeEach(() => keranjang.kosongkan())

  it("menu yang sama dua kali jadi 2 porsi", () => {
    keranjang.tambah("Mie Ayam")
    keranjang.tambah("Mie Ayam")
    expect(get(keranjang)).toEqual([{ nama: "Mie Ayam", porsi: 2 }])
  })

})
// src/routes/pesan/page.server.test.js
import { describe, it, expect } from "vitest"
import { actions } from "./+page.server.js"

function kirim(isian) {
  const request = new Request("http://localhost/pesan", { method: "POST", body: new URLSearchParams(isian) })
  return actions.default({ request })
}

describe("action /pesan", () => {
  it("nama kosong: 400, isiannya dikembalikan", async () => {
    const hasil = await kirim({ nama: "", menu: "2", porsi: "3" })
    expect(hasil.status).toBe(400)
    expect(hasil.data).toEqual({ nama: "", menuId: 2, porsi: 3, pesan: "Nama pemesan wajib diisi" })
  })

  it("pesanan benar: redirect 303 ke terima kasih", async () => {
    await expect(kirim({ nama: "Andi", menu: "2", porsi: "3" })).rejects.toMatchObject({
      status: 303,
      location: "/pesan/terima-kasih",
    })
  })
})

Saat tes gagal

Tombolnya diubah jadi count = 1. Tes pertama tetap lulus, tes kedua gagal, dan laporannya mencetak DOM saat tes menyerah. Itu jawaban tebakannya. Ini bagian atas laporannya (bagian FAIL di bawahnya mengulang pesan yang sama dan menunjuk barisnya di Counter.test.js):

 ✓ src/lib/Counter.test.js > Counter > mulai dari Count: 0 71ms
 × src/lib/Counter.test.js > Counter > dua klik +1 jadi Count: 2 112ms
   → Unable to find an element with the text: Count: 2. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

Ignored nodes: comments, script, style
<body>
  <div>
    <p>
      Count: 1
    </p>
     
    <button>
      +1
    </button>
    
  </div>
</body>

 Test Files  1 failed (1)
      Tests  1 failed | 1 passed (2)

Baca dari atas: tes mana yang gagal (×), apa yang dicari (Count: 2), lalu apa yang sebenarnya ada (Count: 1). Klik kedua gak menambah lagi, jadi bug-nya ada di handler tombol.

userEvent: klik seperti manusia

@testing-library/user-event (pasang dengan npm install -D @testing-library/user-event) mengklik seperti pengguna sungguhan: pointer turun, fokus, pointer naik, lalu klik. Semua method-nya async:

import userEvent from "@testing-library/user-event"

const user = userEvent.setup()
render(Counter)
await user.click(screen.getByRole("button", { name: "+1" }))

Tanpa await, tes memeriksa sebelum kliknya terjadi. Dicoba: user.click(tombol) dua kali tanpa await gagal, dan DOM yang dicetak masih Count: 0. Dengan fireEvent.click di Svelte 5, tes yang lupa await kebetulan masih lulus; tetap tulis await di keduanya.

Proyek baru: SvelteKit 3 dan Svelte 5

Di proyek dari npx sv create, Counter-nya ditulis let count = $state(0) dan onclick, dan Counter.test.js yang sama lulus tanpa diubah satu baris pun, karena tes cuma membaca DOM. Komponen gaya Svelte 4 di proyek itu butuh <svelte:options runes={false} /> di baris pertamanya, karena runes dinyalakan untuk semua file.

Latihan: menulis tes Counter

Editor latihan punya test runner kecil yang meniru Vitest dan @testing-library/svelte: describe, it, expect dengan matcher yang umum (termasuk toBeInTheDocument), render, screen.getByText, screen.getByRole, fireEvent, dan userEvent. Editornya berisi Counter.svelte di atas dan, mulai baris // Counter.test.js, tes-mu. Pratinjau menampilkan laporan seperti Vitest: ✓ atau × per tes, lalu Tests 2 passed (2).

Setelah semua tes lulus, editor menjalankan tesmu lagi pada salinan Counter.svelte yang sengaja dirusak sedikit, misalnya let count = 1 atau count -= 1. Cara ini namanya mutation testing, seperti alat Stryker. Tes yang bagus gagal pada setiap salinan rusak (killed). Salinan yang lolos (survived) menunjukkan hal yang belum dicek tesmu: tes yang cuma membaca Count: 0 meloloskan count -= 1, jadi periksa juga Count: 2 setelah dua klik.

⚠️ Jebakan yang sering ketemu

TL;DR: Tes Svelte memakai Vitest sebagai test runner, @testing-library/svelte untuk render dan mencari elemen, dan jsdom sebagai DOM tiruan; svelteTesting() di vite.config.js dan environment: "jsdom" menyiapkannya. render(Counter) memasang komponen (props lewat { props: {...} }), screen.getByText("Count: 0") dan screen.getByRole("button", { name: "+1" }) mencarinya seperti pengguna, await fireEvent.click(...) atau await user.click(...) dari userEvent mengklik, dan expect(...).toBeInTheDocument() memeriksa. Store dites tanpa render dengan get(store), action dipanggil dengan Request buatan dan redirect diperiksa lewat rejects. Laporan yang gagal mencetak apa yang dicari dan DOM yang sebenarnya. Tes membaca DOM, jadi tes yang sama lulus untuk komponen Svelte 4 dan runes. Latihannya: menulis dua tes untuk Counter, Count: 0 di awal dan Count: 2 setelah dua klik, lalu mutation testing memastikan tesnya menangkap salinan yang rusak.

Yang akan kamu pelajari