Kode async adalah kode yang hasilnya datang belakangan: request ke server, timer setTimeout, membaca file. Di JavaScript, hasil yang belum datang dibungkus Promise. Bayangin pesan makanan di food court: kasir memberimu buzzer, makanannya datang nanti. Buzzer itu Promise-nya. Kalau kamu langsung memotret nampan begitu buzzer di tangan, nampannya masih kosong. Test async juga begitu: test harus menunggu buzzer bunyi dulu, baru memeriksa isinya.
🧪 Tebakan cepat:
ambilData("/kosong")memberi data kosong. Test di bawah memeriksa datanya berisi dua nama, tapi lewat.thentanpaawait. Vitest menandainya ✓ (lulus) atau × (gagal)? Tebak dulu, jawabannya ada di bagian "Lupa menunggu: test lulus palsu".
it("data /kosong berisi dua nama", () => {
ambilData("/kosong").then((hasil) => {
expect(hasil.data).toHaveLength(2)
})
})
Promise: janji yang selesai belakangan
Ini function yang dipakai sepanjang materi ini. Dia pura-pura mengambil data dari server:
function ambilData(url) {
return new Promise((resolve, reject) => {
if (url.includes("users")) {
resolve({ data: ["Budi", "Siti"] });
} else if (url.includes("error")) {
reject(new Error("Gagal mengambil data"));
} else {
resolve({ data: [] });
}
});
}
new Promise((resolve, reject) => { ... })bikin Promise. Di dalamnya kamu memutuskan hasilnya.resolve(nilai)artinya berhasil: Promise selesai membawanilai.reject(new Error("..."))artinya gagal: Promise ditolak membawa error itu.url.includes("users")menjawabtruekalau teksurlmemuat katausers.
| Pemanggilan | Hasil Promise-nya |
|---|---|
ambilData("/users") |
resolve { data: ["Budi", "Siti"] } |
ambilData("/error") |
reject Error("Gagal mengambil data") |
ambilData("/kosong") |
resolve { data: [] } |
async dan await di dalam test
import { describe, it, expect } from "vitest"
describe("ambilData", () => {
it("memberi dua nama untuk /users", async () => {
const hasil = await ambilData("/users")
expect(hasil.data).toEqual(["Budi", "Siti"])
})
})
async () => { ... }: callback test ditandaiasync, supaya boleh memakaiawaitdi dalamnya. Tanpaasync,awaitdi situ adalah syntax error dan file test-nya gak jalan sama sekali.await ambilData("/users")menunggu Promise-nya selesai, lalu mengambil isinya:{ data: ["Budi", "Siti"] }.- Function
asyncselalu mengembalikan Promise. Vitest melihat Promise itu dan menunggunya selesai sebelum memutuskan test-nya lulus atau gagal.
Kalau await-nya lupa, hasil berisi Promise-nya sendiri (buzzer-nya), bukan datanya. hasil.data jadi undefined, dan Vitest menulis:
AssertionError: expected undefined to deeply equal [ 'Budi', 'Siti' ]
resolves dan rejects: periksa Promise langsung
it("memberi dua nama untuk /users", async () => {
await expect(ambilData("/users")).resolves.toEqual({ data: ["Budi", "Siti"] })
})
it("ditolak untuk /error", async () => {
await expect(ambilData("/error")).rejects.toThrow("Gagal mengambil data")
})
.resolvesmenunggu Promise-nya selesai, lalu menjalankan matcher-nya (toEqual,toBe) pada isinya..rejectsjustru mengharapkan Promise-nya ditolak.toThrow("Gagal mengambil data")mengecek pesan error-nya memuat teks itu.awaitdi depanexpecttetap ditulis. Vitest 4 masih menunggu sendiri kalau lupa, tapi mencetak peringatanPromise returned by ... was not awaited, dan versi berikutnya akan menggagalkan test-nya.- Kalau Promise-nya malah resolve,
.rejectsgagal denganAssertionError: promise resolved "{ data: [] }" instead of rejecting.
Menangkap Promise yang ditolak dengan try/catch
Tanpa Vitest pun kamu bisa menguji Promise. Pola ini yang dipakai di latihan: hitung sendiri kasus yang lulus.
async function testAmbilData() {
let passed = 0
try {
await ambilData("/error")
} catch (e) {
if (e.message === "Gagal mengambil data") passed++
}
// dua kasus lain: "/users" (data.length 2) dan url lain (data.length 0)
return passed
}
testAmbilData().then((jumlah) => console.log(jumlah))
awaitpada Promise yang ditolak melempar error-nya, jadi bungkus dengantry/catch. Tanpa itu, function-nya berhenti di situ.e.messageberisi teks error-nya,"Gagal mengambil data".testAmbilDatasendiriasync, jadi hasilnya juga Promise. Angkanya dicetak lewat.then((jumlah) => console.log(jumlah)). Kalau ketiga kasus benar, yang tercetak3.
Lupa menunggu: test lulus palsu
Ini jawaban tebakannya: ✓, lulus, padahal datanya kosong. Vitest 4 menulis:
✓ src/ambilData.test.js > ambilData > data /kosong berisi dua nama 2ms
Callback-nya gak async dan gak mengembalikan apa-apa, jadi buat Vitest test-nya selesai begitu function-nya kembali. .then baru jalan sesudahnya, dan expect yang gagal di dalamnya sudah bukan bagian dari test. Karena ambilData di sini selesai seketika, Vitest kebetulan masih menangkap error nyasar itu di blok terpisah, Unhandled Rejection, dengan pesan This might cause false positive tests. Tapi kalau datanya dari server sungguhan yang butuh 300 ms, run-nya sudah selesai duluan dan gak ada yang melapor.
Obatnya: async di callback, lalu await Promise-nya (atau return-kan Promise-nya).
Fake timers: majukan jam tanpa menunggu
Kode dengan setTimeout gak perlu ditunggu sungguhan. Vitest bisa mengganti jam-nya dengan jam palsu yang kamu putar sendiri, seperti mempercepat timer microwave.
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"
function delayedHello(callback) {
setTimeout(() => {
callback("hello")
}, 1000)
}
describe("delayedHello", () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it("memanggil callback setelah 1 detik", () => {
const callback = vi.fn()
delayedHello(callback)
expect(callback).not.toHaveBeenCalled()
vi.advanceTimersByTime(1000)
expect(callback).toHaveBeenCalledWith("hello")
})
})
| Langkah | Jam palsu | callback dipanggil? |
|---|---|---|
delayedHello(callback) |
0 ms | belum, timer-nya dijadwalkan di 1.000 ms |
vi.advanceTimersByTime(999) |
999 ms | belum |
vi.advanceTimersByTime(1) |
1.000 ms | ya, dengan "hello" |
vi.useFakeTimers()menggantisetTimeout,setIntervaldanDatedengan versi yang jam-nya kamu kendalikan.vi.advanceTimersByTime(1000)memajukan jam 1.000 ms dan menjalankan semua timer yang jatuh tempo. Test-nya tetap selesai dalam hitungan milidetik.vi.fn()adalah function palsu dari materi mocking: dia mencatat tiap pemanggilannya.vi.useRealTimers()diafterEachmengembalikan timer asli, supaya test lain gak ikut memakai jam palsu.
Tanpa vi.useFakeTimers(), memajukan jam langsung ditolak:
Error: A function to advance timers was called but the timers APIs are not mocked. Call `vi.useFakeTimers()` in the test file first.
⚠️ Jebakan yang sering ketemu
- Lupa
asyncdi callback test.awaitdi function biasa adalah syntax error, jadi seluruh file test-nya gagal dibaca. - Lupa
awaitdi depanexpect(...).resolvesatau.rejects. Sekarang masih jalan dengan peringatan, nanti gagal. Biasakan selaluawait expect(...). expectdi dalam.thentanpaawaitataureturn. Test-nya ✓ walau hasilnya salah. Test yang gak bisa gagal gak melindungi apa-apa.- Lupa
vi.useRealTimers(). Test berikutnya masih memakai jam palsu, jadisetTimeoutdi sana gak pernah jalan. Taruh diafterEach. - Memeriksa nilai yang kamu ketik sendiri.
expect({ status: "ok" }).toEqual({ status: "ok" })selalu lulus. Periksa hasil pemanggilannya:expect(data)atauexpect(fetchData()).
TL;DR: Promise membawa hasil yang datang belakangan: resolve kalau berhasil, reject kalau gagal. Di test, tandai callback dengan async lalu await Promise-nya sebelum memeriksa. await expect(p).resolves.toEqual(...) memeriksa isi Promise yang berhasil, await expect(p).rejects.toThrow("...") memeriksa yang ditolak. Tanpa Vitest, tangkap penolakan dengan try/catch. expect di dalam .then tanpa await bikin test lulus palsu. Untuk setTimeout, pakai vi.useFakeTimers(), majukan jam dengan vi.advanceTimersByTime(ms), dan kembalikan dengan vi.useRealTimers() di afterEach.