Vitest 断言 #

断言基础 #

expect 函数 #

expect 是断言的核心函数,用于创建断言链:

typescript
import { expect } from 'vitest'

test('basic assertion', () => {
  expect(1 + 1).toBe(2)
})

断言结构 #

text
expect(实际值).匹配器(期望值)

┌─────────────────────────────────────────────────────────────┐
│                      断言结构                                │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  expect(1 + 1)     .toBe(2)                                │
│       │               │                                    │
│       │               └── 匹配器(期望值)                   │
│       │                                                    │
│       └── 实际值                                           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

相等性断言 #

toBe - 严格相等 #

使用 === 比较:

typescript
import { expect, test } from 'vitest'

test('toBe', () => {
  // 基本类型
  expect(1).toBe(1)
  expect('hello').toBe('hello')
  expect(true).toBe(true)
  
  // 对象引用相同
  const obj = { a: 1 }
  expect(obj).toBe(obj)
  
  // 不同对象不相等
  expect({ a: 1 }).not.toBe({ a: 1 })
})

toEqual - 深度相等 #

递归比较对象和数组:

typescript
import { expect, test } from 'vitest'

test('toEqual', () => {
  // 对象深度比较
  expect({ a: 1, b: { c: 2 } }).toEqual({ a: 1, b: { c: 2 } })
  
  // 数组深度比较
  expect([1, 2, 3]).toEqual([1, 2, 3])
  
  // 嵌套结构
  expect({
    name: 'John',
    age: 30,
    hobbies: ['reading', 'coding'],
  }).toEqual({
    name: 'John',
    age: 30,
    hobbies: ['reading', 'coding'],
  })
})

toStrictEqual - 严格深度相等 #

toEqual 更严格:

typescript
import { expect, test } from 'vitest'

test('toStrictEqual', () => {
  // toEqual 通过,但 toStrictEqual 不通过的情况:
  
  // 1. undefined 属性
  expect({ a: 1, b: undefined }).toEqual({ a: 1 })
  expect({ a: 1, b: undefined }).not.toStrictEqual({ a: 1 })
  
  // 2. 稀疏数组
  expect([, 1]).toEqual([undefined, 1])
  expect([, 1]).not.toStrictEqual([undefined, 1])
  
  // 3. 类实例
  class A {}
  expect(new A()).toEqual({})
  expect(new A()).not.toStrictEqual({})
})

真值断言 #

toBeTruthy / toBeFalsy #

typescript
import { expect, test } from 'vitest'

test('truthy and falsy', () => {
  // truthy 值
  expect(1).toBeTruthy()
  expect('hello').toBeTruthy()
  expect([]).toBeTruthy()      // 空数组是 truthy
  expect({}).toBeTruthy()      // 空对象是 truthy
  
  // falsy 值
  expect(0).toBeFalsy()
  expect('').toBeFalsy()
  expect(null).toBeFalsy()
  expect(undefined).toBeFalsy()
  expect(false).toBeFalsy()
  expect(NaN).toBeFalsy()
})

toBeNull / toBeUndefined #

typescript
import { expect, test } from 'vitest'

test('null and undefined', () => {
  // null
  expect(null).toBeNull()
  expect(undefined).not.toBeNull()
  
  // undefined
  expect(undefined).toBeUndefined()
  expect(null).not.toBeUndefined()
  
  // defined
  expect(1).toBeDefined()
  expect(null).toBeDefined()      // null 是 defined
  expect(undefined).not.toBeDefined()
})

toBeNaN #

typescript
import { expect, test } from 'vitest'

test('NaN', () => {
  expect(NaN).toBeNaN()
  expect(0).not.toBeNaN()
  expect(Infinity).not.toBeNaN()
})

数字断言 #

大小比较 #

typescript
import { expect, test } from 'vitest'

test('number comparisons', () => {
  const value = 10
  
  // 大于
  expect(value).toBeGreaterThan(5)
  expect(value).toBeGreaterThan(9)
  
  // 大于等于
  expect(value).toBeGreaterThanOrEqual(10)
  expect(value).toBeGreaterThanOrEqual(5)
  
  // 小于
  expect(value).toBeLessThan(20)
  expect(value).toBeLessThan(11)
  
  // 小于等于
  expect(value).toBeLessThanOrEqual(10)
  expect(value).toBeLessThanOrEqual(20)
})

浮点数比较 #

typescript
import { expect, test } from 'vitest'

test('floating point', () => {
  // 精确比较可能失败
  // expect(0.1 + 0.2).toBe(0.3)  // 失败!
  
  // 使用 toBeCloseTo
  expect(0.1 + 0.2).toBeCloseTo(0.3)
  expect(0.1 + 0.2).toBeCloseTo(0.3, 5)  // 指定精度
  expect(0.1 + 0.2).toBeCloseTo(0.300, 3)
  
  // Math.PI
  expect(Math.PI).toBeCloseTo(3.14159, 5)
})

字符串断言 #

包含匹配 #

typescript
import { expect, test } from 'vitest'

test('string contains', () => {
  const str = 'Hello, World!'
  
  // 包含子串
  expect(str).toContain('World')
  expect(str).toContain('Hello')
  
  // 不包含
  expect(str).not.toContain('Goodbye')
})

正则匹配 #

typescript
import { expect, test } from 'vitest'

test('string match', () => {
  const str = 'Hello, World!'
  
  // 匹配正则
  expect(str).toMatch(/Hello/)
  expect(str).toMatch(/World!$/)
  expect(str).toMatch(/^Hello/)
  expect(str).toMatch(/[A-Z][a-z]+/)
  
  // 匹配字符串
  expect(str).toMatch('Hello')
})

字符串长度 #

typescript
import { expect, test } from 'vitest'

test('string length', () => {
  const str = 'Hello'
  
  // 使用 toHaveProperty
  expect(str).toHaveProperty('length', 5)
  
  // 或使用 length 属性
  expect(str.length).toBe(5)
})

数组断言 #

包含元素 #

typescript
import { expect, test } from 'vitest'

test('array contains', () => {
  const arr = [1, 2, 3, 'hello', { a: 1 }]
  
  // 包含元素
  expect(arr).toContain(1)
  expect(arr).toContain('hello')
  
  // 包含对象(引用相等)
  const obj = { a: 1 }
  expect([...arr, obj]).toContain(obj)
  
  // 包含相等对象
  expect(arr).toContainEqual({ a: 1 })
})

数组长度 #

typescript
import { expect, test } from 'vitest'

test('array length', () => {
  const arr = [1, 2, 3]
  
  expect(arr).toHaveLength(3)
  expect([]).toHaveLength(0)
})

数组相等 #

typescript
import { expect, test } from 'vitest'

test('array equality', () => {
  // 深度相等
  expect([1, 2, 3]).toEqual([1, 2, 3])
  
  // 包含子集
  expect([1, 2, 3, 4, 5]).toEqual(expect.arrayContaining([1, 2, 3]))
})

对象断言 #

属性存在 #

typescript
import { expect, test } from 'vitest'

test('object properties', () => {
  const obj = {
    name: 'John',
    age: 30,
    address: {
      city: 'New York',
      country: 'USA',
    },
  }
  
  // 有属性
  expect(obj).toHaveProperty('name')
  expect(obj).toHaveProperty('age')
  
  // 属性值
  expect(obj).toHaveProperty('name', 'John')
  expect(obj).toHaveProperty('age', 30)
  
  // 嵌套属性(使用点号)
  expect(obj).toHaveProperty('address.city')
  expect(obj).toHaveProperty('address.city', 'New York')
  
  // 嵌套属性(使用数组)
  expect(obj).toHaveProperty(['address', 'city'])
  expect(obj).toHaveProperty(['address', 'country'], 'USA')
})

对象匹配 #

typescript
import { expect, test } from 'vitest'

test('object matching', () => {
  const obj = {
    name: 'John',
    age: 30,
    email: 'john@example.com',
  }
  
  // 完全匹配
  expect(obj).toEqual({
    name: 'John',
    age: 30,
    email: 'john@example.com',
  })
  
  // 部分匹配
  expect(obj).toMatchObject({
    name: 'John',
    age: 30,
  })
  
  // 嵌套匹配
  expect({
    user: { name: 'John', age: 30 },
    settings: { theme: 'dark' },
  }).toMatchObject({
    user: { name: 'John' },
    settings: { theme: 'dark' },
  })
})

对象包含 #

typescript
import { expect, test } from 'vitest'

test('object contains', () => {
  const obj = { a: 1, b: 2, c: 3 }
  
  // 包含属性
  expect(obj).toHaveProperty('a')
  
  // 包含键
  expect(Object.keys(obj)).toContain('a')
  
  // 包含值
  expect(Object.values(obj)).toContain(1)
})

函数断言 #

函数调用 #

typescript
import { expect, test, vi } from 'vitest'

test('function calls', () => {
  const mockFn = vi.fn()
  
  mockFn()
  mockFn('hello')
  mockFn('hello', 'world')
  
  // 被调用
  expect(mockFn).toHaveBeenCalled()
  
  // 调用次数
  expect(mockFn).toHaveBeenCalledTimes(3)
  
  // 调用参数
  expect(mockFn).toHaveBeenCalledWith('hello')
  expect(mockFn).toHaveBeenLastCalledWith('hello', 'world')
  expect(mockFn).toHaveBeenNthCalledWith(2, 'hello')
})

返回值 #

typescript
import { expect, test, vi } from 'vitest'

test('return values', () => {
  const mockFn = vi.fn()
    .mockReturnValueOnce(1)
    .mockReturnValueOnce(2)
    .mockReturnValue(3)
  
  expect(mockFn()).toBe(1)
  expect(mockFn()).toBe(2)
  expect(mockFn()).toBe(3)
  expect(mockFn()).toBe(3)
})

异常断言 #

同步异常 #

typescript
import { expect, test } from 'vitest'

test('throws error', () => {
  // 函数抛出异常
  expect(() => {
    throw new Error('Something went wrong')
  }).toThrow()
  
  // 抛出特定错误
  expect(() => {
    throw new Error('Something went wrong')
  }).toThrow('Something went wrong')
  
  // 正则匹配
  expect(() => {
    throw new Error('Something went wrong')
  }).toThrow(/wrong/)
  
  // 错误类型
  expect(() => {
    throw new TypeError('Invalid type')
  }).toThrow(TypeError)
})

异步异常 #

typescript
import { expect, test } from 'vitest'

test('async throws', async () => {
  // 异步函数抛出异常
  await expect(async () => {
    throw new Error('Async error')
  }).rejects.toThrow()
  
  // Promise reject
  await expect(Promise.reject(new Error('Rejected'))).rejects.toThrow()
  
  // 特定错误
  await expect(Promise.reject(new Error('Not found'))).rejects.toThrow('Not found')
})

自定义错误类 #

typescript
import { expect, test } from 'vitest'

class ValidationError extends Error {
  constructor(message: string, public field: string) {
    super(message)
    this.name = 'ValidationError'
  }
}

test('custom error', () => {
  expect(() => {
    throw new ValidationError('Invalid email', 'email')
  }).toThrow(ValidationError)
  
  // 检查错误实例
  try {
    throw new ValidationError('Invalid email', 'email')
  } catch (error) {
    expect(error).toBeInstanceOf(ValidationError)
    expect(error).toHaveProperty('field', 'email')
  }
})

类型断言 #

instanceof #

typescript
import { expect, test } from 'vitest'

test('instanceof', () => {
  class Person {}
  const person = new Person()
  
  expect(person).toBeInstanceOf(Person)
  expect([]).toBeInstanceOf(Array)
  expect({}).toBeInstanceOf(Object)
})

typeof #

typescript
import { expect, test } from 'vitest'

test('typeof', () => {
  expect(typeof 'hello').toBe('string')
  expect(typeof 123).toBe('number')
  expect(typeof true).toBe('boolean')
  expect(typeof {}).toBe('object')
  expect(typeof []).toBe('object')  // 数组 typeof 也是 'object'
  expect(typeof (() => {})).toBe('function')
  expect(typeof undefined).toBe('undefined')
})

否定断言 #

使用 not #

typescript
import { expect, test } from 'vitest'

test('negation', () => {
  // 否定断言
  expect(1).not.toBe(2)
  expect('hello').not.toContain('world')
  expect([1, 2, 3]).not.toContain(4)
  expect({ a: 1 }).not.toEqual({ a: 2 })
  
  // 链式否定
  expect(null).not.toBeUndefined()
  expect(undefined).not.toBeNull()
})

异步断言 #

resolves #

typescript
import { expect, test } from 'vitest'

test('resolves', async () => {
  // Promise resolve
  await expect(Promise.resolve(1)).resolves.toBe(1)
  
  // 异步函数
  await expect(fetchUser(1)).resolves.toHaveProperty('name')
  
  // 复杂断言
  await expect(Promise.resolve({ a: 1, b: 2 }))
    .resolves.toMatchObject({ a: 1 })
})

rejects #

typescript
import { expect, test } from 'vitest'

test('rejects', async () => {
  // Promise reject
  await expect(Promise.reject(new Error('Failed'))).rejects.toThrow()
  
  // 特定错误
  await expect(Promise.reject(new Error('Not found')))
    .rejects.toThrow('Not found')
})

断言修饰符 #

.not #

typescript
import { expect, test } from 'vitest'

test('not modifier', () => {
  expect(1).not.toBe(2)
  expect(true).not.toBeFalsy()
})

.resolves / .rejects #

typescript
import { expect, test } from 'vitest'

test('promise modifiers', async () => {
  await expect(Promise.resolve(1)).resolves.toBe(1)
  await expect(Promise.reject(new Error())).rejects.toThrow()
})

期望工具 #

expect.anything() #

匹配任何非 null/undefined 值:

typescript
import { expect, test } from 'vitest'

test('expect.anything', () => {
  expect({ a: 1 }).toEqual({ a: expect.anything() })
  expect({ a: null }).not.toEqual({ a: expect.anything() })
  expect({ a: undefined }).not.toEqual({ a: expect.anything() })
})

expect.any() #

匹配特定类型的任何值:

typescript
import { expect, test } from 'vitest'

test('expect.any', () => {
  // 任何数字
  expect({ age: 30 }).toEqual({ age: expect.any(Number) })
  
  // 任何字符串
  expect({ name: 'John' }).toEqual({ name: expect.any(String) })
  
  // 任何数组
  expect({ items: [1, 2, 3] }).toEqual({ items: expect.any(Array) })
  
  // 自定义类
  class User {}
  expect({ user: new User() }).toEqual({ user: expect.any(User) })
})

expect.arrayContaining() #

匹配包含特定元素的数组:

typescript
import { expect, test } from 'vitest'

test('expect.arrayContaining', () => {
  expect([1, 2, 3, 4, 5]).toEqual(expect.arrayContaining([1, 2, 3]))
  expect(['a', 'b', 'c']).toEqual(expect.arrayContaining(['a', 'c']))
  
  // 与 toMatchObject 结合
  expect([
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
  ]).toEqual(expect.arrayContaining([
    { id: 1, name: 'John' },
  ]))
})

expect.objectContaining() #

匹配包含特定属性的对象:

typescript
import { expect, test } from 'vitest'

test('expect.objectContaining', () => {
  expect({ a: 1, b: 2, c: 3 }).toEqual(expect.objectContaining({ a: 1 }))
  expect({ name: 'John', age: 30 }).toEqual(expect.objectContaining({ name: 'John' }))
  
  // 嵌套对象
  expect({
    user: { name: 'John', age: 30 },
    settings: { theme: 'dark' },
  }).toEqual(expect.objectContaining({
    user: expect.objectContaining({ name: 'John' }),
  }))
})

expect.stringContaining() #

匹配包含特定子串的字符串:

typescript
import { expect, test } from 'vitest'

test('expect.stringContaining', () => {
  expect('Hello, World!').toEqual(expect.stringContaining('World'))
  expect('Hello, World!').toEqual(expect.stringContaining('Hello'))
})

expect.stringMatching() #

匹配正则表达式的字符串:

typescript
import { expect, test } from 'vitest'

test('expect.stringMatching', () => {
  expect('Hello, World!').toEqual(expect.stringMatching(/World/))
  expect('email@example.com').toEqual(expect.stringMatching(/@/))
  expect('2024-01-15').toEqual(expect.stringMatching(/^\d{4}-\d{2}-\d{2}$/))
})

expect.closeTo() #

匹配接近的浮点数:

typescript
import { expect, test } from 'vitest'

test('expect.closeTo', () => {
  expect(0.1 + 0.2).toEqual(expect.closeTo(0.3))
  expect(Math.PI).toEqual(expect.closeTo(3.14159, 5))
})

expect.extend() #

自定义匹配器:

typescript
import { expect, test } from 'vitest'

// 扩展匹配器
expect.extend({
  toBeWithinRange(received: number, floor: number, ceiling: number) {
    const pass = received >= floor && received <= ceiling
    return {
      pass,
      message: () => 
        `expected ${received} ${pass ? 'not to' : 'to'} be within range ${floor} - ${ceiling}`,
    }
  },
  
  toBeEven(received: number) {
    const pass = received % 2 === 0
    return {
      pass,
      message: () => `expected ${received} ${pass ? 'not to' : 'to'} be even`,
    }
  },
})

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

断言最佳实践 #

1. 使用描述性断言 #

typescript
// 好的做法
test('user should have valid email', () => {
  const user = createUser('john@example.com')
  expect(user.email).toMatch(/@/)
})

// 不好的做法
test('user email', () => {
  const user = createUser('john@example.com')
  expect(user.email).toBeTruthy()
})

2. 一个测试一个断言主题 #

typescript
// 好的做法
test('user has correct properties', () => {
  const user = { name: 'John', age: 30 }
  expect(user).toMatchObject({
    name: 'John',
    age: 30,
  })
})

// 不好的做法
test('user properties', () => {
  const user = { name: 'John', age: 30 }
  expect(user.name).toBe('John')
  expect(user.age).toBe(30)
  expect(user).toHaveProperty('name')
  expect(user).toHaveProperty('age')
})

3. 使用类型安全的断言 #

typescript
import { expect, test } from 'vitest'

test('type safe assertions', () => {
  const value: unknown = 'hello'
  
  // 类型断言
  if (typeof value === 'string') {
    expect(value).toBe('hello')
    expect(value.length).toBe(5)
  }
})

断言速查表 #

匹配器 说明
toBe(value) 严格相等 (===)
toEqual(value) 深度相等
toStrictEqual(value) 严格深度相等
toBeTruthy() 真值
toBeFalsy() 假值
toBeNull() null
toBeUndefined() undefined
toBeDefined() 已定义
toBeNaN() NaN
toBeGreaterThan(n) 大于
toBeGreaterThanOrEqual(n) 大于等于
toBeLessThan(n) 小于
toBeLessThanOrEqual(n) 小于等于
toBeCloseTo(n, digits?) 浮点数近似
toContain(item) 包含元素
toContainEqual(item) 包含相等元素
toHaveLength(n) 长度
toHaveProperty(path, value?) 有属性
toMatchObject(obj) 对象匹配
toMatch(regexp) 正则匹配
toThrow(error?) 抛出异常
toBeInstanceOf(Class) 实例类型
toHaveBeenCalled() 被调用
toHaveBeenCalledTimes(n) 调用次数
toHaveBeenCalledWith(...args) 调用参数

下一步 #

现在你已经掌握了断言的使用方法,接下来学习 Mock 功能 了解如何模拟函数和模块!

最后更新:2026-03-28