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