Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Vitest 3.x reference skill covering configuration, test/describe APIs, mocking, coverage, snapshots, and concurrency.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/core-expect.md
1---2name: expect-api3description: Assertions with matchers, asymmetric matchers, and custom matchers4---56# Expect API78Vitest uses Chai assertions with Jest-compatible API.910## Basic Assertions1112```ts13import { expect, test } from 'vitest'1415test('assertions', () => {16// Equality17expect(1 + 1).toBe(2) // Strict equality (===)18expect({ a: 1 }).toEqual({ a: 1 }) // Deep equality1920// Truthiness21expect(true).toBeTruthy()22expect(false).toBeFalsy()23expect(null).toBeNull()24expect(undefined).toBeUndefined()25expect('value').toBeDefined()2627// Numbers28expect(10).toBeGreaterThan(5)29expect(10).toBeGreaterThanOrEqual(10)30expect(5).toBeLessThan(10)31expect(0.1 + 0.2).toBeCloseTo(0.3, 5)3233// Strings34expect('hello world').toMatch(/world/)35expect('hello').toContain('ell')3637// Arrays38expect([1, 2, 3]).toContain(2)39expect([{ a: 1 }]).toContainEqual({ a: 1 })40expect([1, 2, 3]).toHaveLength(3)4142// Objects43expect({ a: 1, b: 2 }).toHaveProperty('a')44expect({ a: 1, b: 2 }).toHaveProperty('a', 1)45expect({ a: { b: 1 } }).toHaveProperty('a.b', 1)46expect({ a: 1 }).toMatchObject({ a: 1 })4748// Types49expect('string').toBeTypeOf('string')50expect(new Date()).toBeInstanceOf(Date)51})52```5354## Negation5556```ts57expect(1).not.toBe(2)58expect({ a: 1 }).not.toEqual({ a: 2 })59```6061## Error Assertions6263```ts64// Sync errors - wrap in function65expect(() => throwError()).toThrow()66expect(() => throwError()).toThrow('message')67expect(() => throwError()).toThrow(/pattern/)68expect(() => throwError()).toThrow(CustomError)6970// Async errors - use rejects71await expect(asyncThrow()).rejects.toThrow('error')72```7374## Promise Assertions7576```ts77// Resolves78await expect(Promise.resolve(1)).resolves.toBe(1)79await expect(fetchData()).resolves.toEqual({ data: true })8081// Rejects82await expect(Promise.reject('error')).rejects.toBe('error')83await expect(failingFetch()).rejects.toThrow()84```8586## Spy/Mock Assertions8788```ts89const fn = vi.fn()90fn('arg1', 'arg2')91fn('arg3')9293expect(fn).toHaveBeenCalled()94expect(fn).toHaveBeenCalledTimes(2)95expect(fn).toHaveBeenCalledWith('arg1', 'arg2')96expect(fn).toHaveBeenLastCalledWith('arg3')97expect(fn).toHaveBeenNthCalledWith(1, 'arg1', 'arg2')9899expect(fn).toHaveReturned()100expect(fn).toHaveReturnedWith(value)101102// v4 additions103expect(fn).toHaveBeenCalledExactlyOnceWith('arg1', 'arg2')104expect(fnA).toHaveBeenCalledBefore(fnB)105expect(fnA).toHaveBeenCalledAfter(fnB)106```107108### Chai-Style Spy Assertions (4.1+)109110Sinon-chai-compatible aliases, useful when migrating from Sinon:111112```ts113expect(spy).to.have.been.called114expect(spy).to.have.been.calledOnce115expect(spy).to.have.been.calledWith('arg1', 'arg2')116expect(spy).to.have.been.calledOnceWith('arg')117```118119### Conditional Mock Exhaustion (v5)120121Assert every `vi.when` behavior was consumed:122123```ts124const w = vi.when(spy).calledWith(1).thenReturnOnce('a')125spy(1)126expect(w).toHaveBeenExhausted()127```128129## Asymmetric Matchers130131Use inside `toEqual`, `toHaveBeenCalledWith`, etc:132133```ts134expect({ id: 1, name: 'test' }).toEqual({135id: expect.any(Number),136name: expect.any(String),137})138139expect({ a: 1, b: 2, c: 3 }).toEqual(140expect.objectContaining({ a: 1 })141)142143expect([1, 2, 3, 4]).toEqual(144expect.arrayContaining([1, 3])145)146147expect('hello world').toEqual(148expect.stringContaining('world')149)150151expect('hello world').toEqual(152expect.stringMatching(/world$/)153)154155expect({ value: null }).toEqual({156value: expect.anything() // Matches anything except null/undefined157})158159// Negate with expect.not160expect([1, 2]).toEqual(161expect.not.arrayContaining([3])162)163164// toBeOneOf - value matches any option (great for optional props)165expect(user).toEqual({166name: expect.any(String),167middleName: expect.toBeOneOf([expect.any(String), undefined]),168})169170// schemaMatching (4.0+) - matches any Standard Schema (Zod, Valibot, ArkType)171import { z } from 'zod'172expect(payload).toEqual({173email: expect.schemaMatching(z.string().email()),174})175expect(repo.save).toHaveBeenCalledWith(expect.schemaMatching(UserSchema))176```177178## Soft Assertions179180Prefer `expect.soft` for **non-critical assertions** — it marks the test failed but continues so all failures are reported together:181182```ts183expect.soft(response.status).toBe(200) // non-critical, keeps going184expect.soft(response.headers.get('x-id')).toBeTruthy()185expect(response.body).toBeDefined() // critical: hard expect stops on failure186```187188## Type-Narrowing Assertions (4.0+)189190`expect.assert` throws at runtime **and** narrows the TypeScript type (unlike `toBeTruthy`/`toBeDefined`, which return `void`):191192```ts193const user = cache.get('alice') // { id, name } | undefined194expect.assert(user) // throws if undefined, narrows below195expect(user.name).toBe('Alice') // no `!`, no `as`196197// Narrows typeof / instanceof too198expect.assert(typeof input === 'string')199input.toUpperCase()200201// Chai assert helpers via the same namespace202expect.assert.isDefined(maybeUser)203expect.assert.instanceOf(error, MyError)204```205206## Poll Assertions207208Retry until passes:209210```ts211await expect.poll(() => fetchStatus()).toBe('ready')212213await expect.poll(214() => document.querySelector('.element'),215{ interval: 100, timeout: 5000 }216).toBeTruthy()217```218219## Assertion Count220221```ts222test('async assertions', async () => {223expect.assertions(2) // Exactly 2 assertions must run224225await doAsync((data) => {226expect(data).toBeDefined()227expect(data.id).toBe(1)228})229})230231test('at least one', () => {232expect.hasAssertions() // At least 1 assertion must run233})234```235236## Extending Matchers237238```ts239expect.extend({240toBeWithinRange(received, floor, ceiling) {241const pass = received >= floor && received <= ceiling242return {243pass,244message: () =>245`expected ${received} to be within range ${floor} - ${ceiling}`,246}247},248})249250test('custom matcher', () => {251expect(100).toBeWithinRange(90, 110)252})253```254255## Snapshot Assertions256257```ts258expect(data).toMatchSnapshot()259expect(data).toMatchInlineSnapshot(`{ "id": 1 }`)260await expect(result).toMatchFileSnapshot('./expected.json')261262expect(() => throw new Error('fail')).toThrowErrorMatchingSnapshot()263```264265## Key Points266267- Use `toBe` for primitives, `toEqual` for objects/arrays268- `toStrictEqual` checks undefined properties and array sparseness269- Always `await` async assertions (`resolves`, `rejects`, `poll`)270- Use context's `expect` in concurrent tests for correct tracking271- `toThrow` requires wrapping sync code in a function272- Use `expect.soft` for non-critical assertions; reserve hard `expect` for must-pass conditions273- Use `expect.assert` (not `toBeTruthy`) when you also need TypeScript narrowing274275<!--276Source references:277- https://vitest.dev/api/expect.html278- https://vitest.dev/guide/recipes/type-narrowing279- https://vitest.dev/guide/recipes/schema-matching280-->281