All skills
antfu avatar

/vitest

@d02c484 official
by Anthony Fuantfu/skills5.9k stars
335

Vitest fast unit testing framework powered by Vite with Jest-compatible API. Use when writing tests, mocking, configuring coverage, or working with test filtering and fixtures.

Use this Skill: https://skilld.dev/gh/antfu/skills/vitest

This session only. Nothing lands on disk.

referencescore-expect.md

≈2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Expect API

Vitest uses Chai assertions with Jest-compatible API.

Basic Assertions

import { expect, test } from 'vitest'

test('assertions', () => {
  // Equality
  expect(1 + 1).toBe(2)              // Strict equality (===)
  expect({ a: 1 }).toEqual({ a: 1 }) // Deep equality

  // Truthiness
  expect(true).toBeTruthy()
  expect(false).toBeFalsy()
  expect(null).toBeNull()
  expect(undefined).toBeUndefined()
  expect('value').toBeDefined()

  // Numbers
  expect(10).toBeGreaterThan(5)
  expect(10).toBeGreaterThanOrEqual(10)
  expect(5).toBeLessThan(10)
  expect(0.1 + 0.2).toBeCloseTo(0.3, 5)

  // Strings
  expect('hello world').toMatch(/world/)
  expect('hello').toContain('ell')

  // Arrays
  expect([1, 2, 3]).toContain(2)
  expect([{ a: 1 }]).toContainEqual({ a: 1 })
  expect([1, 2, 3]).toHaveLength(3)

  // Objects
  expect({ a: 1, b: 2 }).toHaveProperty('a')
  expect({ a: 1, b: 2 }).toHaveProperty('a', 1)
  expect({ a: { b: 1 } }).toHaveProperty('a.b', 1)
  expect({ a: 1 }).toMatchObject({ a: 1 })

  // Types
  expect('string').toBeTypeOf('string')
  expect(new Date()).toBeInstanceOf(Date)
})

Negation

expect(1).not.toBe(2)
expect({ a: 1 }).not.toEqual({ a: 2 })

Error Assertions

// Sync errors - wrap in function
expect(() => throwError()).toThrow()
expect(() => throwError()).toThrow('message') // string = substring of the message
expect(() => throwError()).toThrow(/pattern/)
expect(() => throwError()).toThrow(CustomError)

// v5: toThrow('') matches ANY message (empty string is a substring of all).
// To assert an empty message, match the pattern: .toThrow(/^$/)

// Async errors - use rejects
await expect(asyncThrow()).rejects.toThrow('error')

Promise Assertions

// Resolves
await expect(Promise.resolve(1)).resolves.toBe(1)
await expect(fetchData()).resolves.toEqual({ data: true })

// Rejects
await expect(Promise.reject('error')).rejects.toBe('error')
await expect(failingFetch()).rejects.toThrow()

v5: unawaited async assertions (resolves, rejects, toMatchFileSnapshot) now fail the test — v4 only printed a warning and auto-awaited at the end. Always await them.

Spy/Mock Assertions

const fn = vi.fn()
fn('arg1', 'arg2')
fn('arg3')

expect(fn).toHaveBeenCalled()
expect(fn).toHaveBeenCalledTimes(2)
expect(fn).toHaveBeenCalledWith('arg1', 'arg2')
expect(fn).toHaveBeenLastCalledWith('arg3')
expect(fn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2')

expect(fn).toHaveReturned()
expect(fn).toHaveReturnedWith(value)

// v4 additions
expect(fn).toHaveBeenCalledExactlyOnceWith('arg1', 'arg2')
expect(fnA).toHaveBeenCalledBefore(fnB)
expect(fnA).toHaveBeenCalledAfter(fnB)

Chai-Style Spy Assertions (4.1+)

Sinon-chai-compatible aliases, useful when migrating from Sinon:

expect(spy).to.have.been.called
expect(spy).to.have.been.calledOnce
expect(spy).to.have.been.calledWith('arg1', 'arg2')
expect(spy).to.have.been.calledOnceWith('arg')

Conditional Mock Exhaustion (v5)

Assert every vi.when behavior was consumed:

const w = vi.when(spy).calledWith(1).thenReturnOnce('a')
spy(1)
expect(w).toHaveBeenExhausted()

Asymmetric Matchers

Use inside toEqual, toHaveBeenCalledWith, etc:

expect({ id: 1, name: 'test' }).toEqual({
  id: expect.any(Number),
  name: expect.any(String),
})

expect({ a: 1, b: 2, c: 3 }).toEqual(
  expect.objectContaining({ a: 1 })
)

expect([1, 2, 3, 4]).toEqual(
  expect.arrayContaining([1, 3])
)

expect('hello world').toEqual(
  expect.stringContaining('world')
)

expect('hello world').toEqual(
  expect.stringMatching(/world$/)
)

expect({ value: null }).toEqual({
  value: expect.anything() // Matches anything except null/undefined
})

// Negate with expect.not
expect([1, 2]).toEqual(
  expect.not.arrayContaining([3])
)

// toBeOneOf - value matches any option (great for optional props)
expect(user).toEqual({
  name: expect.any(String),
  middleName: expect.toBeOneOf([expect.any(String), undefined]),
})

// schemaMatching (4.0+) - matches any Standard Schema (Zod, Valibot, ArkType)
import { z } from 'zod'
expect(payload).toEqual({
  email: expect.schemaMatching(z.string().email()),
})
expect(repo.save).toHaveBeenCalledWith(expect.schemaMatching(UserSchema))

Soft Assertions

Prefer expect.soft for non-critical assertions — it marks the test failed but continues so all failures are reported together:

expect.soft(response.status).toBe(200) // non-critical, keeps going
expect.soft(response.headers.get('x-id')).toBeTruthy()
expect(response.body).toBeDefined() // critical: hard expect stops on failure

Type-Narrowing Assertions (4.0+)

expect.assert throws at runtime and narrows the TypeScript type (unlike toBeTruthy/toBeDefined, which return void):

const user = cache.get('alice') // { id, name } | undefined
expect.assert(user)             // throws if undefined, narrows below
expect(user.name).toBe('Alice') // no `!`, no `as`

// Narrows typeof / instanceof too
expect.assert(typeof input === 'string')
input.toUpperCase()

// Chai assert helpers via the same namespace
expect.assert.isDefined(maybeUser)
expect.assert.instanceOf(error, MyError)

Poll Assertions

Retry until passes:

await expect.poll(() => fetchStatus()).toBe('ready')

await expect.poll(
  () => document.querySelector('.element'),
  { interval: 100, timeout: 5000 }
).toBeTruthy()

v5: expect.poll now rejects when it times out (v4 could still pass on a late attempt). The callback receives an AbortSignal that aborts on timeout so you can cancel in-flight work:

await expect.poll(async ({ signal }) => {
  const res = await fetch('/api/status', { signal })
  return res.status
}, { timeout: 1000 }).toBe(200)

Assertion Count

test('async assertions', async () => {
  expect.assertions(2) // Exactly 2 assertions must run
  
  await doAsync((data) => {
    expect(data).toBeDefined()
    expect(data.id).toBe(1)
  })
})

test('at least one', () => {
  expect.hasAssertions() // At least 1 assertion must run
})

Extending Matchers

expect.extend({
  toBeWithinRange(received, floor, ceiling) {
    const pass = received >= floor && received <= ceiling
    return {
      pass,
      message: () => 
        `expected ${received} to be within range ${floor} - ${ceiling}`,
    }
  },
})

test('custom matcher', () => {
  expect(100).toBeWithinRange(90, 110)
})

TypeScript Declarations (v5)

The Matchers interface now takes the return type first (R) and the received type second (T). R is void synchronously, Promise<void> through .resolves/.rejects/expect.poll/expect.element:

import 'vitest'

declare module 'vitest' {
  interface Matchers<R, T> {
    toBeFoo: () => R
    toEqualTyped: (expected: T) => R // T mirrors the received value's type
  }
}

Referencing assertion types directly also needs the return type first: Assertion<void, string> (sync) / Assertion<Promise<void>, string> (async). v5 no longer reads matchers from the global jest.Matchers interface — augment vitest.Matchers separately.

Snapshot Assertions

expect(data).toMatchSnapshot()
expect(data).toMatchInlineSnapshot(`{ "id": 1 }`)
await expect(result).toMatchFileSnapshot('./expected.json')

expect(() => throw new Error('fail')).toThrowErrorMatchingSnapshot()

Key Points

  • Use toBe for primitives, toEqual for objects/arrays
  • toStrictEqual checks undefined properties and array sparseness
  • Always await async assertions (resolves, rejects, poll)
  • Use context's expect in concurrent tests for correct tracking
  • toThrow requires wrapping sync code in a function
  • Use expect.soft for non-critical assertions; reserve hard expect for must-pass conditions
  • Use expect.assert (not toBeTruthy) when you also need TypeScript narrowing
<!-- Source references: - https://vitest.dev/api/expect.html - https://vitest.dev/guide/recipes/type-narrowing - https://vitest.dev/guide/recipes/schema-matching -->

Source: SKILL.md on GitHub

No alerts3d5 checks · Risk SAFE
  • Gen Agent Trust Hub3d

    The skill provides comprehensive documentation and reference material for the Vitest testing framework. It covers configuration, CLI usage, mocking, coverage, and advanced features like type testing and benchmarking. No security issues were detected; the instructions follow standard development and testing practices.

  • Socket3d

    No alerts

  • Snyk3d

    Risk: LOW · No issues

  • Runlayer7mo

    1/18 files flagged

  • ZeroLeaks5mo

    Score: 93/100 · 2 sections analyzed

Signed by skilld at d02c484. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 days ago.

Activeupdated 4 days ago
Other metadata
metadata
{
  "author": "Anthony Fu",
  "version": "2026.9.25",
  "source": "Generated from https://github.com/vitest-dev/vitest, scripts located at https://github.com/antfu/skills"
}

README badge

README badge for antfu/skills/vitest

Vitest is a Jest-compatible unit testing framework powered by Vite, with native ESM, TypeScript, and JSX support. Use it for writing tests with smart watch mode, mocking, fixtures, coverage reporting, and snapshot testing in projects already using Vite.

Generated from the current SKILL.md.

Is Vitest a drop-in replacement for Jest?
Mostly. Vitest provides a Jest-compatible API and can run most Jest test suites without modification, but it uses Vite's transformation pipeline instead of Jest's, so some edge cases may differ.
Does Vitest require configuration?
No. It natively supports ESM, TypeScript, and JSX out of the box and shares Vite's config, transformers, and resolvers, so many projects need minimal or no additional setup.
What coverage providers does Vitest support?
Vitest includes built-in coverage via V8 or Istanbul providers.
Can I run tests in parallel?
Yes. Vitest uses multi-threaded workers for parallel test execution and supports sharding across multiple processes.
What test environments are available?
Vitest supports node, jsdom, happy-dom, and custom environments.

Generated from the current SKILL.md. These answers refresh after source changes.