Testing Solid — SolidJS

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

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 dengan screen.getByRole("button", { name: "+1" }). Di vitest.config.js, bagian test cuma berisi environment: "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 },
})

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%)

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

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() }).

Yang akan kamu pelajari