Episode ini membangun kebiasaan testing: unit testing hooks Query dengan renderHook, testing navigasi Router, testing render tabel dan list virtual, serta integration testing dengan mock server memakai MSW.

Fitur yang berhasil di development bisa gagal total di production jika tidak diuji. TanStack punya satu keuntungan besar untuk testing: karena library headless dan stateful, logika query, navigasi, dan tabel bisa diuji tanpa bergantung pada server nyata.
Episode 16 membahas unit testing hooks Query, testing navigasi Router, testing render tabel dan list virtual, serta integration testing dengan mock server memakai MSW.
Target episode ini: kalian punya suite test yang cepat, deterministik, dan bisa dijalankan di CI — persis yang kita butuhkan untuk pipeline di episode 19.
Mulai dengan menginstall perangkat uji:
npm i -D vitest @testing-library/react @testing-library/jest-dom jsdomvitest adalah test runner cepat yang terintegrasi dengan Vite. @testing-library/react menyediakan render dan renderHook, jest-dom menambah matcher DOM, dan jsdom meniru lingkungan browser di Node.js.
Hook query diuji dengan membungkusnya dalam provider yang membuat QueryClient baru per test. Ini menjaga isolasi cache antar test:
import { renderHook, waitFor } from "@testing-library/react"
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
function wrapper({ children }) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
})
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
}
test("memuat data pengguna", async () => {
const { result } = renderHook(
() => useQuery({
queryKey: ["pengguna"],
queryFn: async () => ({ nama: "Arman" }),
}),
{ wrapper }
)
await waitFor(() => expect(result.current.isSuccess).toBe(true))
expect(result.current.data.nama).toBe("Arman")
})retry: false di default options memastikan test tidak menunggu retry saat query gagal — kegagalan muncul cepat. renderHook dengan wrapper membuat query diuji persis seperti berjalan di aplikasi.
Router diuji dengan membuat instance router dari route tree yang sama dengan produksi, lalu me-render RouterProvider:
import { render, screen } from "@testing-library/react"
import { RouterProvider, createRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree"
const router = createRouter({ routeTree })
test("menampilkan halaman utama", async () => {
render(<RouterProvider router={router} />)
expect(await screen.findByText("Halaman utama")).toBeInTheDocument()
})createRouter({ routeTree }) memakai tree yang sama persis dengan aplikasi. Dengan findByText, test menunggu loader selesai dan halaman tampil — sekaligus memverifikasi bahwa navigasi dan loader bekerja end-to-end.
Tabel dan list virtual hanya merender sebagian baris. Test harus menegaskan bahwa baris yang terlihat muncul dan item di luar viewport tidak dirender — inilah perilaku virtualisasi itu sendiri:
test("merender sebagian baris saja", () => {
const { container } = render(<DaftarVirtual count={1000} />)
const baris = container.querySelectorAll("div.baris")
expect(baris.length).toBeLessThan(50)
})Dengan 1000 item, harapkan node DOM baris jauh lebih sedikit dari 1000. toBeLessThan(50) membuktikan virtualisasi aktif. Untuk table state, uji lewat interaksi: klik header untuk sorting, klik halaman untuk pagination, lalu cek baris yang muncul.
Untuk integration test, mock seluruh backend dengan MSW. Query function memakai fetch biasa; MSW mencegatnya dan mengembalikan data palsu:
import { setupServer } from "msw/node"
import { http, HttpResponse } from "msw"
const server = setupServer(
http.get("/api/todos", () =>
HttpResponse.json([{ id: 1, judul: "Belajar TanStack" }])
)
)
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())setupServer meniru server HTTP di Node.js. Test kemudian bisa me-render komponen penuh, melakukan mutasi, dan memverifikasi invalidasi query — semuanya tanpa jaringan nyata. resetHandlers menjaga skenario antar test tetap terisolasi.
Tip
Dalam test, gunakan fake timers dan advanceTimersByTime untuk menguji refetchInterval dan retryDelay tanpa menunggu waktu nyata. Ini membuat suite test tetap cepat.
Episode 16 menutup quality assurance: hooks Query diuji dengan renderHook dan wrapper, navigasi Router diuji lewat RouterProvider, tabel dan list virtual diuji lewat jumlah node DOM, dan seluruh alur diuji end-to-end dengan MSW sebagai mock server.
Inti yang harus dibawa pulang:
Di episode 17 selanjutnya kita akan membahas TypeScript dan schema safety — strong typing di Query dan Table, pola inference dan utility types, type-safe data loading di Router, serta validasi schema dengan Zod. Semua kode kalian akan menjadi jauh lebih aman!