Vitest Cheatsheet

Async Tests

Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Async/Await

import { it, expect } from 'vitest'

it('fetches data', async () => {
  const data = await fetchData()
  expect(data).toEqual({ id: 1 })
})

Returning Promises

it('returns a promise', () => {
  return fetchData().then((data) => {
    expect(data.id).toBe(1)
  })
})

Vitest waits for any returned promise to resolve or reject.

Testing Async Errors

// Option 1: async/await + try/catch
it('throws async error', async () => {
  await expect(fetchData(999)).rejects.toThrow('Not found')
})

// Option 2: .rejects.toThrow
it('rejects with an error', async () => {
  await expect(fetchData(-1)).rejects.toBeInstanceOf(TypeError)
})

// Option 3: rejects.toMatchObject
it('rejects with shape', async () => {
  await expect(asyncFn()).rejects.toMatchObject({ code: 404 })
})

Always await the .rejects chain — forgetting causes the test to pass even when it should fail.

Resolved Assertions

it('resolves with value', async () => {
  await expect(Promise.resolve(42)).resolves.toBe(42)
  await expect(fetchUser(1)).resolves.toMatchObject({ name: 'Alice' })
})

Callback APIs (no done callback)

Vitest does not support Jest's done callback — the first argument of a test function is the test context, so it('...', (done) => { done() }) throws (done is not a function). Wrap callback APIs in a Promise instead:

it('calls the callback', async () => {
  const result = await new Promise((resolve, reject) => {
    fetchWithCallback((err, value) => (err ? reject(err) : resolve(value)))
  })
  expect(result).toBe('ok')
})

Node-style (err, value) callbacks can also be converted with util.promisify:

import { promisify } from 'node:util'

it('promisified callback', async () => {
  const fetchAsync = promisify(fetchWithCallback)
  await expect(fetchAsync()).resolves.toBe('ok')
})

Add expect.assertions(n) when the assertion runs inside a callback, so the test fails if the callback never fires.

Async Timeouts

it('slow operation', async () => {
  const result = await longTask()
  expect(result).toBeDefined()
}, 15_000) // 15-second timeout

// Or globally:
// test: { testTimeout: 15_000 }

Async Hooks

beforeAll(async () => {
  await db.connect()
})

afterAll(async () => {
  await db.disconnect()
})

beforeEach(async () => {
  await db.seed()
})

waitFor — Polling Until Condition

import { waitFor } from '@testing-library/react' // RTL variant
// or use vi.waitFor from Vitest 1.x+:

import { vi } from 'vitest'

it('waits for condition', async () => {
  startProcess()
  await vi.waitFor(() => {
    expect(getStatus()).toBe('done')
  }, { timeout: 3000, interval: 100 })
})

vi.waitFor retries the callback until it stops throwing or the timeout is reached.

vi.waitUntil

it('waits for truthy value', async () => {
  const result = await vi.waitUntil(
    () => getAsyncValue(), // retries until not null/undefined/false
    { timeout: 2000, interval: 50 }
  )
  expect(result).toBe('ready')
})

Fake Timers with Async

it('resolves after delay', async () => {
  vi.useFakeTimers()

  const promise = delay(5000).then(() => 'done')

  vi.advanceTimersByTimeAsync(5000) // async-safe version

  const result = await promise
  expect(result).toBe('done')

  vi.useRealTimers()
})

Use advanceTimersByTimeAsync (not advanceTimersByTime) when promises depend on microtask resolution.

Concurrent Async Tests

it.concurrent('test 1', async () => {
  const data = await fetchA()
  expect(data).toBeDefined()
})

it.concurrent('test 2', async () => {
  const data = await fetchB()
  expect(data).toBeDefined()
})

Or for the whole suite:

describe.concurrent('parallel async', () => {
  it('a', async () => { /* ... */ })
  it('b', async () => { /* ... */ })
})

Checking Assertions Count for Async

it('assert count enforces async path', async () => {
  expect.assertions(2)

  await asyncFn().then((result) => {
    expect(result.a).toBeDefined()
    expect(result.b).toBeDefined()
  })
})

Common Gotchas

  • Missing await on .rejects/.resolves — test silently passes without checking.
  • expect.assertions(n) with callbacks — prevents false positives when callbacks are never called.
  • Fake timer + Promise ordering — always use advanceTimersByTimeAsync when promises are in the chain.
  • Unhandled rejections — Vitest treats these as test failures in Node 15+.
  • async beforeAll not awaited — Vitest awaits hooks automatically when they return a promise.