Test adalah kode yang memeriksa kodemu secara otomatis: dia memasang komponen, menekan tombolnya seperti pengguna, lalu mencocokkan apa yang tampil. Untuk Solid, alatnya Vitest (yang menjalankan tes) dan @solidjs/testing-library (yang memasang komponen dan mencarinya seperti pengguna). Bayangin uji coba mesin kasir Toko Buku Kak Nadia tiap pagi sebelum toko buka: petugas men-scan satu buku contoh, lalu melihat apakah layar menampilkan angka yang benar. Kalau salah, ketahuan sebelum pembeli pertama datang.
🧪 Tebakan cepat: dua tes sama-sama memanggil
render(() => <Counter />), dan tes kedua mencari tombol denganscreen.getByRole("button", { name: "+1" }). Divitest.config.js, bagiantestcuma berisienvironment: "jsdom". Tes kedua lolos atau gagal? Jawabannya ada di bagian "Cleanup: tiap tes mulai dari halaman bersih".
Setup
npm install -D vitest jsdom @solidjs/testing-library @testing-library/user-event @testing-library/jest-dom
// vitest.config.js
import { defineConfig } from "vitest/config"
import solid from "vite-plugin-solid"
export default defineConfig({
plugins: [solid()],
test: { environment: "jsdom", globals: true },
})
@solidjs/testing-libraryadalah library testing resmi Solid, versi Solid dari React Testing Library. Dulu namanyasolid-testing-library.vite-plugin-solidsudah ada di proyek Vite Solid. Dia mengubah JSX di file tes, sama seperti di aplikasinya.environment: "jsdom"memberi tes sebuahdocumentpalsu di Node, jadi komponen bisa dipasang tanpa browser.globals: truedibahas di bagian cleanup.
Komponen yang dites
Ini latihannya:
// src/Counter.jsx
import { createSignal } from "solid-js"
export default function Counter() {
const [count, setCount] = createSignal(0)
return (
<div>
<p>Count: {count()}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</div>
)
}
Komponen yang mudah dites menampilkan keadaannya di layar: <p> berbunyi Count: 0, sekali klik +1 jadi Count: 1, dua kali jadi Count: 2.
Tes pertama: render, screen, fireEvent
// src/Counter.test.jsx
import { describe, it, expect } from "vitest"
import { render, screen, fireEvent } from "@solidjs/testing-library"
import userEvent from "@testing-library/user-event"
import "@testing-library/jest-dom/vitest"
import Counter from "./Counter"
describe("Counter", () => {
it("mulai dari Count: 0", () => {
render(() => <Counter />)
expect(screen.getByText("Count: 0")).toBeInTheDocument()
})
it("+1 sekali jadi Count: 1", () => {
render(() => <Counter />)
fireEvent.click(screen.getByRole("button", { name: "+1" }))
expect(screen.getByText("Count: 1")).toBeInTheDocument()
})
it("+1 dua kali jadi Count: 2", async () => {
const user = userEvent.setup()
render(() => <Counter />)
await user.click(screen.getByRole("button", { name: "+1" }))
await user.click(screen.getByRole("button", { name: "+1" }))
expect(screen.getByText("Count: 2")).toBeInTheDocument()
})
})
npx vitest run, ditangkap dari proyek sungguhan (Vitest 5, @solidjs/testing-library 0.8):
RUN v5.0.3 /home/nadia/toko-buku
Test Files 1 passed (1)
Tests 3 passed (3)
Start at 19:30:15
Duration 1.06s (environment 66%, transform 10%, tests 10%, setup 10%, import 4%, worker 1%)
render(() => <Counter />)memasang komponennya ke<div>baru didocument.body. Argumennya fungsi, supayarendersendiri yang menjalankanCounterdi dalam root-nya, jadi reactive scope (pemilik) terbentuk dengan benar: efek bisa dibongkar dan context dari Provider sampai. Diuji denganrender(<Counter />)tanpa fungsi: console mencetakcomputations created outside a `createRoot` or `render` will never be disposed, dan Provider dariwrappergak sampai ke komponennya.screen.getByText("Count: 0")mencari elemen yang teksnya persis itu.screen.getByRole("button", { name: "+1" })mencari seperti pembaca layar: perannya tombol, namanya+1.fireEvent.click(...)mengirim satu event klik. Solid memperbarui DOM saat itu juga, jadi baris berikutnya langsung bisa memeriksa.userEventmeniru pengguna lebih lengkap (pointer, fokus, lalu klik), dan dipanggil denganawait.toBeInTheDocument()datang dari@testing-library/jest-dom/vitest.
Saat tesnya gagal
Ganti tombolnya jadi setCount((c) => c + 2). Potongan output aslinya:
FAIL src/Counter.test.jsx > Counter > +1 sekali jadi Count: 1
TestingLibraryElementError: Unable to find an element with the text: Count: 1. 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>
<div>
<p>
Count:
2
</p>
Tesnya menunjukkan isi halaman saat gagal: Count: 2, bukan Count: 1. Bug khas Solid juga tertangkap: const angka = count() di badan komponen lalu <p>Count: {angka}</p> menggagalkan dua tes yang sama, dengan Count: dan 0 di dump-nya, karena angkanya dibaca sekali.
Cleanup: tiap tes mulai dari halaman bersih
Tanpa globals: true, tes kedua gagal. Itu jawaban tebakannya. Output aslinya:
FAIL src/Counter.test.jsx > Counter > +1 sekali jadi Count: 1
TestingLibraryElementError: Found multiple elements with the role "button" and name "+1"
@solidjs/testing-library membongkar komponen sesudah tiap tes lewat afterEach milik Vitest, tapi afterEach cuma ada kalau globals: true. Tanpa itu, Counter dari tes pertama masih di document.body, dan getByRole menemukan dua tombol. Pilih salah satu: globals: true, atau afterEach(cleanup) dengan cleanup dari @solidjs/testing-library.
Menguji primitive di dalam createRoot
Primitive dari pelajaran Custom Primitives dites tanpa tampilan. Efek dan memo butuh pemilik, jadi bungkus dengan createRoot:
import { it, expect } from "vitest"
import { createRoot } from "solid-js"
import { createCounter } from "./createCounter"
it("increment, decrement, reset", () => {
createRoot((dispose) => {
const counter = createCounter(10)
counter.increment()
expect(counter.count()).toBe(11)
counter.decrement()
counter.decrement()
expect(counter.count()).toBe(9)
counter.reset()
expect(counter.count()).toBe(10)
dispose()
})
})
dispose() di akhir membongkar semua yang dibuat primitive-nya, timer dan onCleanup termasuk. @solidjs/testing-library juga punya renderHook untuk hal yang sama.
⚠️ Jebakan yang sering ketemu
render(<Counter />)tanpa fungsi. Komponennya dibuat sebelumrendermenyiapkan root-nya: muncul peringatan tadi, dan context dari Provider gak sampai.- Lupa
globals: true(atauafterEach(cleanup)). Komponen tes sebelumnya tertinggal, jadigetBy...menemukan lebih dari satu elemen. - Komponen yang menyimpan keadaannya diam-diam. Tes membaca layar. Latihan memeriksa
<p>berbunyi persisCount: 0, laluCount: 1sesudah satu klik danCount: 2sesudah dua klik, jadi kenaikannya harus berlanjut dari nilai lama:setCount((c) => c + 1).
TL;DR: Tes Solid memakai Vitest dengan environment: "jsdom" dan globals: true, plus @solidjs/testing-library. render(() => <Counter />) (selalu fungsi, supaya reactive scope terbentuk dengan benar) memasang komponennya, screen.getByText dan screen.getByRole("button", { name: "+1" }) mencarinya seperti pengguna, fireEvent.click atau await user.click menekannya, dan expect(...).toBeInTheDocument() memeriksa hasilnya. Tes yang gagal mencetak isi halaman saat itu. globals: true membuat tiap tes mulai dari halaman bersih. Primitive dites di dalam createRoot((dispose) => { ...; dispose() }).