expect
以下类型在下面的类型签名中被使用。
type Awaitable<T> = T | PromiseLike<T>
expect
用于创建断言。在这种情况下, assertions
是可以调用来断言语句的函数。 Vitest 默认提供 chai
断言,并且还在 chai
之上构建了与 Jest
兼容的断言。
例如,此代码断言 input
值等于 2
。 如果不是,assertions 将抛出错误,并且测试将失败。
import { expect } from 'vitest'
const input = Math.sqrt(4)
expect(input).to.equal(2) // chai API
expect(input).toBe(2) // jest API
从技术上讲,这个示例没有使用 test
函数,因此在控制台中你将看到 Nodejs 错误而不是 Vitest 输出。 要了解更多关于 test
的信息,请阅读测试 API 参考。
此外,expect
可以静态地使用来访问匹配器函数,稍后将会介绍。
WARNING
如果表达式没有类型错误,则 expect
对测试类型没有影响。 如果你想使用 Vitest 作为类型检查器,请使用 expectTypeOf
或 assertType
。
soft
- 类型:
ExpectStatic & (actual: any) => Assertions
expect.soft
的功能与 expect
类似,但它不会在断言失败时终止测试执行,而是继续运行并将失败标记为测试失败。 测试过程中遇到的所有错误都会显示出来,直到测试完成。
import { expect, test } from 'vitest'
test('expect.soft test', () => {
expect.soft(1 + 1).toBe(3) // mark the test as fail and continue
expect.soft(1 + 2).toBe(4) // mark the test as fail and continue
})
// reporter will report both errors at the end of the run
它也可以与 expect
一起使用。 如果 expect
断言失败,测试将终止并显示所有错误。
import { expect, test } from 'vitest'
test('expect.soft test', () => {
expect.soft(1 + 1).toBe(3) // mark the test as fail and continue
expect(1 + 2).toBe(4) // failed and terminate the test, all previous errors will be output
expect.soft(1 + 3).toBe(5) // do not run
})
WARNING
expect.soft
只能在 test
函数的内部使用。
poll
interface ExpectPoll extends ExpectStatic {
(actual: () => T, options: { interval; timeout; message }): Promise<Assertions<T>>
}
expect.poll
重新运行断言,直到成功为止。你可以通过设置 interval
和 timeout
选项来配置 Vitest 应重新运行 expect.poll
回调的次数。
如果在 expect.poll
回调中抛出错误,Vitest 将重试直到超时为止。
import { expect, test } from 'vitest'
test('element exists', async () => {
asyncInjectElement()
await expect.poll(() => document.querySelector('.element')).toBeTruthy()
})
WARNING
expect.poll
使每个断言都异步,所以不要忘记等待它,否则可能会收到未经处理的 promise 拒绝。
expect.poll
不适用于多个匹配器:
- 快照匹配器不受支持,因为它们总是成功的。如果你的情况不稳定,请考虑首先使用
vi.waitFor
解决:
import { expect, vi } from 'vitest'
const flakyValue = await vi.waitFor(() => getFlakyValue())
expect(flakyValue).toMatchSnapshot()
.resolves
和.rejects
不支持。 如果它是异步的,expect.poll
已经在等待。toThrow
及其别名不受支持,因为expect.poll
条件总是在匹配器获取值之前解析。
not
使用 not
将否定该断言。 例如,此代码断言 input
值不等于 2
。 如果相等,断言将抛出错误,测试将失败。
import { expect, test } from 'vitest'
const input = Math.sqrt(16)
expect(input).not.to.equal(2) // chai API
expect(input).not.toBe(2) // jest API
toBe
- 类型:
(value: any) => Awaitable<void>
toBe
可用于断言基元是否相等或对象共享相同的引用。 它相当于调用 expect(Object.is(3, 3)).toBe(true)
。 如果对象不相同,但你想检查它们的结构是否相同,可以使用 toEqual
。
例如,下面的代码检查交易者是否有 13 个苹果。
import { expect, test } from 'vitest'
const stock = {
type: 'apples',
count: 13,
}
test('stock has 13 apples', () => {
expect(stock.type).toBe('apples')
expect(stock.count).toBe(13)
})
test('stocks are the same', () => {
const refStock = stock // same reference
expect(stock).toBe(refStock)
})
尽量不要将 toBe
与浮点数一起使用。 由于 JavaScript 对它们进行四舍五入,因此 0.1 + 0.2
并不严格是 0.3
。 要可靠地断言浮点数,请使用 toBeCloseTo
断言。
toBeCloseTo
- 类型:
(value: number, numDigits?: number) => Awaitable<void>
使用 toBeCloseTo
比较浮点数。可选的 numDigits
参数限制了小数点后要检查的位数。例如:
import { expect, test } from 'vitest'
test.fails('decimals are not equal in javascript', () => {
expect(0.2 + 0.1).toBe(0.3) // 0.2 + 0.1 is 0.30000000000000004
})
test('decimals are rounded to 5 after the point', () => {
// 0.2 + 0.1 is 0.30000 | "000000000004" removed
expect(0.2 + 0.1).toBeCloseTo(0.3, 5)
// nothing from 0.30000000000000004 is removed
expect(0.2 + 0.1).not.toBeCloseTo(0.3, 50)
})
toBeDefined
- 类型:
() => Awaitable<void>
toBeDefined
断言值不等于 undefined
。有用的用例是检查函数是否有返回任何内容。
import { expect, test } from 'vitest'
function getApples() {
return 3
}
test('function returned something', () => {
expect(getApples()).toBeDefined()
})
toBeUndefined
- 类型:
() => Awaitable<void>
与 toBeDefined
相反,toBeUndefined
断言值 is 等于 undefined
。有用的用例是检查函数是否没有返回任何东西。
import { expect, test } from 'vitest'
function getApplesFromStock(stock: string) {
if (stock === 'Bill') {
return 13
}
}
test('mary doesn\'t have a stock', () => {
expect(getApplesFromStock('Mary')).toBeUndefined()
})
toBeTruthy
- 类型:
() => Awaitable<void>
toBeTruthy
断言值在转换为布尔值时为 true。如果你不关心值,只想知道它可以转换为true
,这将非常有用。
例如,假设有以下代码,我们不关心 stocks.getInfo
的返回值 - 它可能是一个复杂对象、一个字符串或其他任何值。代码仍然可以正常工作。
import { Stocks } from './stocks.js'
const stocks = new Stocks()
stocks.sync('Bill')
if (stocks.getInfo('Bill')) {
stocks.sell('apples', 'Bill')
}
因此,如果要测试 stocks.getInfo
是否真实,可以这样写:
import { expect, test } from 'vitest'
import { Stocks } from './stocks.js'
const stocks = new Stocks()
test('if we know Bill stock, sell apples to him', () => {
stocks.sync('Bill')
expect(stocks.getInfo('Bill')).toBeTruthy()
})
除了 false
、null
、undefined
、NaN
、0
、-0
、0n
、""
和 document.all
以外,JavaScript 中的一切都是真实的。
toBeFalsy
- 类型:
() => Awaitable<void>
toBeFalsy
断言值在转换为布尔值时为 false。如果你不关心值,只想知道它可以转换为false
,这将非常有用。
例如,假设有以下代码,我们不关心 stocks.stockFailed
的返回值 - 它可能返回任何假值,但代码仍然可以正常工作。
import { Stocks } from './stocks.js'
const stocks = new Stocks()
stocks.sync('Bill')
if (!stocks.stockFailed('Bill')) {
stocks.sell('apples', 'Bill')
}
因此,如果要测试stocks.stockFailed
是否是虚假的,可以这样写:
import { expect, test } from 'vitest'
import { Stocks } from './stocks.js'
const stocks = new Stocks()
test('if Bill stock hasn\'t failed, sell apples to him', () => {
stocks.syncStocks('Bill')
expect(stocks.stockFailed('Bill')).toBeFalsy()
})
除了 false
、null
、undefined
、NaN
、0
、-0
、0n
、""
和 document.all
以外,JavaScript 中的一切都是真实的。
toBeNull
- 类型:
() => Awaitable<void>
toBeNull
只是断言某些内容是否为 null
。 .toBe(null)
的别名。
import { expect, test } from 'vitest'
function apples() {
return null
}
test('we don\'t have apples', () => {
expect(apples()).toBeNull()
})
toBeNaN
- 类型:
() => Awaitable<void>
toBeNaN
简单地断言某些内容是否为 NaN
。toBe(NaN)` 的别名。
import { expect, test } from 'vitest'
let i = 0
function getApplesCount() {
i++
return i > 1 ? Number.NaN : i
}
test('getApplesCount has some unusual side effects...', () => {
expect(getApplesCount()).not.toBeNaN()
expect(getApplesCount()).toBeNaN()
})
toBeTypeOf
- 类型:
(c: 'bigint' | 'boolean' | 'function' | 'number' | 'object' | 'string' | 'symbol' | 'undefined') => Awaitable<void>
toBeTypeOf
断言实际值是否属于接收类型。
import { expect, test } from 'vitest'
const actual = 'stock'
test('stock is type of string', () => {
expect(actual).toBeTypeOf('string')
})
toBeInstanceOf
- 类型:
(c: any) => Awaitable<void>
toBeInstanceOf
断言实际值是否是接收类的实例。
import { expect, test } from 'vitest'
import { Stocks } from './stocks.js'
const stocks = new Stocks()
test('stocks are instance of Stocks', () => {
expect(stocks).toBeInstanceOf(Stocks)
})
toBeGreaterThan
- 类型:
(n: number | bigint) => Awaitable<void>
toBeGreaterThan
断言实际值是否大于接收值。如果数值相等,则测试失败。
import { expect, test } from 'vitest'
import { getApples } from './stocks.js'
test('have more then 10 apples', () => {
expect(getApples()).toBeGreaterThan(10)
})
toBeGreaterThanOrEqual
- 类型:
(n: number | bigint) => Awaitable<void>
toBeGreaterThanOrEqual
断言实际值是否大于或等于接收值。
import { expect, test } from 'vitest'
import { getApples } from './stocks.js'
test('have 11 apples or more', () => {
expect(getApples()).toBeGreaterThanOrEqual(11)
})
toBeLessThan
- 类型:
(n: number | bigint) => Awaitable<void>
toBeLessThan
断言实际值是否小于接收值。如果数值相等,则测试失败。
import { expect, test } from 'vitest'
import { getApples } from './stocks.js'
test('have less then 20 apples', () => {
expect(getApples()).toBeLessThan(20)
})
toBeLessThanOrEqual
- 类型:
(n: number | bigint) => Awaitable<void>
toBeLessThanOrEqual
断言实际值小于接收值或等于接收值。
import { expect, test } from 'vitest'
import { getApples } from './stocks.js'
test('have 11 apples or less', () => {
expect(getApples()).toBeLessThanOrEqual(11)
})
toEqual
- 类型:
(received: any) => Awaitable<void>
toEqual
断言实际值是否等于接收到的值,或者如果它是一个对象,则是否具有相同的结构(递归比较它们)。我们可以通过以下示例看到 toEqual
与 toBe
之间的区别:
import { expect, test } from 'vitest'
const stockBill = {
type: 'apples',
count: 13,
}
const stockMary = {
type: 'apples',
count: 13,
}
test('stocks have the same properties', () => {
expect(stockBill).toEqual(stockMary)
})
test('stocks are not the same', () => {
expect(stockBill).not.toBe(stockMary)
})
WARNING
不会对 Error
对象执行 deep equality 。只有 Error 的 message
属性才被视为相等。要自定义相等以检查 message
以外的属性,请使用 expect.addEqualityTesters
。要测试是否抛出了错误,需要使用 toThrowError
断言。
toStrictEqual
- 类型:
(received: any) => Awaitable<void>
toStrictEqual
断言实际值是否等于接收到的值,或者如果它是一个对象(递归地比较它们)并且具有相同的类型,则具有相同的结构。
与 .toEqual
的区别:
- 检查具有
undefined
属性的键。 例如 使用.toStrictEqual
时,{a: undefined, b: 2}
与{b: 2}
不匹配。 - 检查数组稀疏性。 例如 使用
.toStrictEqual
时,[, 1]
与[undefined, 1]
不匹配。 - 检查对象类型是否相等。 例如 具有字段
a
和b
的类实例不等于具有字段a
和b
的文字对象。
import { expect, test } from 'vitest'
class Stock {
constructor(type) {
this.type = type
}
}
test('structurally the same, but semantically different', () => {
expect(new Stock('apples')).toEqual({ type: 'apples' })
expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' })
})
toContain
- 类型:
(received: string) => Awaitable<void>
toContain
断言实际值是否在数组中。toContain
还可以检查一个字符串是否是另一个字符串的子串。自 Vitest 1.0 起,如果我们需要在类似浏览器的环境中运行测试,此断言还可以检查类是否包含在 classList
中,或一个元素是否包含在另一个元素中。
import { expect, test } from 'vitest'
import { getAllFruits } from './stocks.js'
test('the fruit list contains orange', () => {
expect(getAllFruits()).toContain('orange')
const element = document.querySelector('#el')
// element has a class
expect(element.classList).toContain('flex')
// element is inside another one
expect(document.querySelector('#wrapper')).toContain(element)
})
toContainEqual
- 类型:
(received: any) => Awaitable<void>
toContainEqual
断言数组中是否包含具有特定结构和值的项。 它在每个元素内部的工作方式类似于 toEqual
。
import { expect, test } from 'vitest'
import { getFruitStock } from './stocks.js'
test('apple available', () => {
expect(getFruitStock()).toContainEqual({ fruit: 'apple', count: 5 })
})
toHaveLength
- 类型:
(received: number) => Awaitable<void>
toHaveLength
断言对象是否具有 .length
属性,并且该属性设置为特定的数值。
import { expect, test } from 'vitest'
test('toHaveLength', () => {
expect('abc').toHaveLength(3)
expect([1, 2, 3]).toHaveLength(3)
expect('').not.toHaveLength(3) // doesn't have .length of 3
expect({ length: 3 }).toHaveLength(3)
})
toHaveProperty
- 类型:
(key: any, received?: any) => Awaitable<void>
toHaveProperty
断言对象是否具有提供的引用 key
处的属性。
我们还可以提供一个可选的值参数,也称为深相等,就像 toEqual
匹配器一样,用于比较接收到的属性值。
import { expect, test } from 'vitest'
const invoice = {
'isActive': true,
'P.O': '12345',
'customer': {
first_name: 'John',
last_name: 'Doe',
location: 'China',
},
'total_amount': 5000,
'items': [
{
type: 'apples',
quantity: 10,
},
{
type: 'oranges',
quantity: 5,
},
],
}
test('John Doe Invoice', () => {
expect(invoice).toHaveProperty('isActive') // assert that the key exists
expect(invoice).toHaveProperty('total_amount', 5000) // assert that the key exists and the value is equal
expect(invoice).not.toHaveProperty('account') // assert that this key does not exist
// Deep referencing using dot notation
expect(invoice).toHaveProperty('customer.first_name')
expect(invoice).toHaveProperty('customer.last_name', 'Doe')
expect(invoice).not.toHaveProperty('customer.location', 'India')
// Deep referencing using an array containing the key
expect(invoice).toHaveProperty('items[0].type', 'apples')
expect(invoice).toHaveProperty('items.0.type', 'apples') // dot notation also works
// Deep referencing using an array containing the keyPath
expect(invoice).toHaveProperty(['items', 0, 'type'], 'apples')
expect(invoice).toHaveProperty(['items', '0', 'type'], 'apples') // string notation also works
// Wrap your key in an array to avoid the key from being parsed as a deep reference
expect(invoice).toHaveProperty(['P.O'], '12345')
})
toMatch
- 类型:
(received: string | regexp) => Awaitable<void>
toMatch
断言字符串是否与正则表达式或字符串匹配。
import { expect, test } from 'vitest'
test('top fruits', () => {
expect('top fruits include apple, orange and grape').toMatch(/apple/)
expect('applefruits').toMatch('fruit') // toMatch also accepts a string
})
toMatchObject
- 类型:
(received: object | array) => Awaitable<void>
toMatchObject
断言对象是否匹配另一个对象的部分属性。
我们还可以传递一个对象数组。如果想要检查两个数组在元素数量上是否匹配,这将非常有用,与arrayContaining
不同,后者允许接收到的数组中有额外的元素。
import { expect, test } from 'vitest'
const johnInvoice = {
isActive: true,
customer: {
first_name: 'John',
last_name: 'Doe',
location: 'China',
},
total_amount: 5000,
items: [
{
type: 'apples',
quantity: 10,
},
{
type: 'oranges',
quantity: 5,
},
],
}
const johnDetails = {
customer: {
first_name: 'John',
last_name: 'Doe',
location: 'China',
},
}
test('invoice has john personal details', () => {
expect(johnInvoice).toMatchObject(johnDetails)
})
test('the number of elements must match exactly', () => {
// Assert that an array of object matches
expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([
{ foo: 'bar' },
{ baz: 1 },
])
})
toThrowError
类型:
(received: any) => Awaitable<void>
别名:
toThrow
toThrowError
断言函数在被调用时是否会抛出错误。
我们可以提供一个可选参数来测试是否抛出了特定的错误:
- 正则表达式 (regular expression) :错误消息与模式匹配
- 字符串 (string) :错误消息包含子字符串
TIP
必须将代码包装在一个函数中,否则错误将无法被捕获,测试将失败。
例如,如果我们想要测试 getFruitStock('pineapples')
是否会抛出错误,我们可以这样写:
import { expect, test } from 'vitest'
function getFruitStock(type: string) {
if (type === 'pineapples') {
throw new Error('Pineapples are not in stock')
}
// Do some other stuff
}
test('throws on pineapples', () => {
// Test that the error message says "stock" somewhere: these are equivalent
expect(() => getFruitStock('pineapples')).toThrowError(/stock/)
expect(() => getFruitStock('pineapples')).toThrowError('stock')
// Test the exact error message
expect(() => getFruitStock('pineapples')).toThrowError(
/^Pineapples are not in stock$/
)
})
TIP
要测试异步函数,请与 rejects 结合使用。
function getAsyncFruitStock() {
return Promise.reject(new Error('empty'))
}
test('throws on pineapples', async () => {
await expect(() => getAsyncFruitStock()).rejects.toThrowError('empty')
})
toMatchSnapshot
- 类型:
<T>(shape?: Partial<T> | string, message?: string) => void
这样可以确保一个值与最近的快照匹配。
可以提供一个可选的 hint
字符串参数,它会附加到测试名称的末尾。尽管 Vitest 总是在快照名称的末尾附加一个数字,但简短的描述性提示可能比数字更有用,以区分单个 it 或 test 块中的多个快照。Vitest 会按名称在相应的 .snap
文件中对快照进行排序。
TIP
当快照不匹配导致测试失败时,如果这种不匹配是预期的,我们可以按 u
键一次性更新快照。或者可以传递 -u
或 --update
命令行选项,使 Vitest 始终更新测试。
import { expect, test } from 'vitest'
test('matches snapshot', () => {
const data = { foo: new Set(['bar', 'snapshot']) }
expect(data).toMatchSnapshot()
})
我们还可以提供一个对象的形状,如果我们只是测试对象的形状,而不需要它完全兼容:
import { expect, test } from 'vitest'
test('matches snapshot', () => {
const data = { foo: new Set(['bar', 'snapshot']) }
expect(data).toMatchSnapshot({ foo: expect.any(Set) })
})
toHaveResolvedTimes
- 类型:
(amount: number) => Awaitable<void>
此断言检查函数是否已成功解析值精确次数(即未 reject)。需要将 spy 函数传递给expect
。
这只会计算已 resolved 的 promises。如果函数返回了一个 promise,但尚未 resolved,则不会计算在内。
import { expect, test, vi } from 'vitest'
test('spy function resolved a value two times', async () => {
const sell = vi.fn((product: string) => Promise.resolve({ product }))
await sell('apples')
await sell('bananas')
expect(sell).toHaveResolvedTimes(2)
})
toHaveResolvedWith
- 类型:
(returnValue: any) => Awaitable<void>
您可以调用此断言来检查函数是否至少成功解析过一次某个值。需要将 spy 函数传递给expect
。
如果函数返回了一个 promise,但尚未 resolved,则将会失败。
import { expect, test, vi } from 'vitest'
test('spy function resolved a product', async () => {
const sell = vi.fn((product: string) => Promise.resolve({ product }))
await sell('apples')
expect(sell).toHaveResolvedWith({ product: 'apples' })
})
toHaveLastResolvedWith
- Type:
(returnValue: any) => Awaitable<void>
您可以调用此断言来检查函数在上次调用时是否已成功解析某个值。需要将 spy 函数传递给expect
。
如果函数返回了一个 promise,但尚未 resolved,则将会失败。
import { expect, test, vi } from 'vitest'
test('spy function resolves bananas on a last call', async () => {
const sell = vi.fn((product: string) => Promise.resolve({ product }))
await sell('apples')
await sell('bananas')
expect(sell).toHaveLastResolvedWith({ product: 'bananas' })
})
toHaveNthResolvedWith
- Type:
(time: number, returnValue: any) => Awaitable<void>
您可以调用此断言来检查函数在上次调用时是否已成功解析某个值。需要将 spy 函数传递给expect
。
如果函数返回了一个 promise,但尚未 resolved,则将会失败。
import { expect, test, vi } from 'vitest'
test('spy function returns bananas on second call', async () => {
const sell = vi.fn((product: string) => Promise.resolve({ product }))
await sell('apples')
await sell('bananas')
expect(sell).toHaveNthResolvedWith(2, { product: 'bananas' })
})
toSatisfy
- 类型:
(predicate: (value: any) => boolean) => Awaitable<void>
该断言检查一个值是否满足「某个谓词/certain predicate」。
import { describe, expect, it } from 'vitest'
const isOdd = (value: number) => value % 2 !== 0
describe('toSatisfy()', () => {
it('pass with 0', () => {
expect(1).toSatisfy(isOdd)
})
it('pass with negation', () => {
expect(2).not.toSatisfy(isOdd)
})
})
resolves
- 类型:
Promisify<Assertions>
resolves
旨在在断言异步代码时消除样板代码。使用它来从待定的 Promise 中解包值,并使用通常的断言来断言其值。如果 Promise 被拒绝,断言将失败。
它返回相同的 Assertions
对象,但所有匹配器现在都返回 Promise
,因此我们需要使用 await
。它也适用于 chai
断言。
例如,如果有一个函数,它发出 API 调用并返回一些数据,可以使用以下代码来断言其返回值:
import { expect, test } from 'vitest'
async function buyApples() {
return fetch('/buy/apples').then(r => r.json())
}
test('buyApples returns new stock id', async () => {
// toEqual returns a promise now, so you HAVE to await it
await expect(buyApples()).resolves.toEqual({ id: 1 }) // jest API
await expect(buyApples()).resolves.to.equal({ id: 1 }) // chai API
})
WARNING
如果断言没有被等待,那么将得到一个虚假的测试,它将每次都通过。为了确保断言实际上被调用,需要使用 expect.assertions(number)
。
rejects
- 类型:
Promisify<Assertions>
rejects
旨在在断言异步代码时消除样板代码。使用它来解包 Promise 被拒绝的原因,并使用通常的断言来断言其值。如果 Promise 成功解决,断言将失败。
它返回相同的 Assertions
对象,但所有匹配器现在都返回 Promise
,因此需要使用 await
。它也适用于 chai
断言。
例如,如果有一个在调用时失败的函数,可以使用以下代码来断言失败的原因:
import { expect, test } from 'vitest'
async function buyApples(id) {
if (!id) {
throw new Error('no id')
}
}
test('buyApples throws an error when no id provided', async () => {
// toThrow returns a promise now, so you HAVE to await it
await expect(buyApples()).rejects.toThrow('no id')
})
WARNING
如果不等待断言,那么将得到每次都会通过的误报测试。 为了确保确实调用了断言,可以使用 expect.assertions(number)
。
expect.assertions
- 类型:
(count: number) => void
在测试通过或失败后,验证在测试期间调用了特定数量的断言。一个有用的情况是检查异步代码是否被调用了。
例如,如果我们有一个异步调用了两个匹配器的函数,我们可以断言它们是否真的被调用了。
import { expect, test } from 'vitest'
async function doAsync(...cbs) {
await Promise.all(cbs.map((cb, index) => cb({ index })))
}
test('all assertions are called', async () => {
expect.assertions(2)
function callback1(data) {
expect(data).toBeTruthy()
}
function callback2(data) {
expect(data).toBeTruthy()
}
await doAsync(callback1, callback2)
})
WARNING
在使用异步并发测试时,必须使用本地 Test Context 中的 expect
来确保正确的测试被检测到。
expect.hasAssertions
- 类型:
() => void
在测试通过或失败后,验证在测试期间至少调用了一个断言。一个有用的情况是检查是否调用了异步代码。
例如,如果有一个调用回调的代码,我们可以在回调中进行断言,但是如果我们不检查是否调用了断言,测试将始终通过。
import { expect, test } from 'vitest'
import { db } from './db.js'
const cbs = []
function onSelect(cb) {
cbs.push(cb)
}
// after selecting from db, we call all callbacks
function select(id) {
return db.select({ id }).then((data) => {
return Promise.all(cbs.map(cb => cb(data)))
})
}
test('callback was called', async () => {
expect.hasAssertions()
onSelect((data) => {
// should be called on select
expect(data).toBeTruthy()
})
// if not awaited, test will fail
// if you don't have expect.hasAssertions(), test will pass
await select(3)
})
expect.unreachable
- 类型:
(message?: string) => never
这种方法用于断言某一行永远不会被执行。
例如,如果我们想要测试 build()
因为接收到没有 src
文件夹的目录而抛出异常,并且还要分别处理每个错误,我们可以这样做:
import { expect, test } from 'vitest'
async function build(dir) {
if (dir.includes('no-src')) {
throw new Error(`${dir}/src does not exist`)
}
}
const errorDirs = [
'no-src-folder',
// ...
]
test.each(errorDirs)('build fails with "%s"', async (dir) => {
try {
await build(dir)
expect.unreachable('Should not pass build')
}
catch (err: any) {
expect(err).toBeInstanceOf(Error)
expect(err.stack).toContain('build')
switch (dir) {
case 'no-src-folder':
expect(err.message).toBe(`${dir}/src does not exist`)
break
default:
// to exhaust all error tests
expect.unreachable('All error test must be handled')
break
}
}
})
expect.anything
- 类型:
() => any
该非对称匹配器与相等检查一起使用时,将始终返回 true
。如果只想确定属性是否存在,那么它就很有用。
import { expect, test } from 'vitest'
test('object has "apples" key', () => {
expect({ apples: 22 }).toEqual({ apples: expect.anything() })
})
expect.any
- 类型:
(constructor: unknown) => any
这个不对称的匹配器在与相等性检查一起使用时,只有当该值是指定构造函数的实例时才会返回true
。 如果我们有一个每次生成的值,并且只想知道它是否存在,这将非常有用。
import { expect, test } from 'vitest'
import { generateId } from './generators.js'
test('"id" is a number', () => {
expect({ id: generateId() }).toEqual({ id: expect.any(Number) })
})
expect.closeTo
- 类型:
(expected: any, precision?: number) => any
在比较对象属性或数组项中的浮点数时,expect.closeTo
非常有用。 如果需要比较数字,请改用 .toBeCloseTo
。
可选的 precision
参数限制要检查小数点后的位数。 对于默认值 2
,测试标准为 Math.abs(expected - received) < 0.005 (that is, 10 ** -2 / 2)
。
例如,此测试以 5 位精度通过:
test('compare float in object properties', () => {
expect({
title: '0.1 + 0.2',
sum: 0.1 + 0.2,
}).toEqual({
title: '0.1 + 0.2',
sum: expect.closeTo(0.3, 5),
})
})
expect.arrayContaining
- 类型:
<T>(expected: T[]) => any
与相等检查一起使用时,如果值是数组且包含指定项,则此非对称匹配器将返回 true
。
import { expect, test } from 'vitest'
test('basket includes fuji', () => {
const basket = {
varieties: ['Empire', 'Fuji', 'Gala'],
count: 3,
}
expect(basket).toEqual({
count: 3,
varieties: expect.arrayContaining(['Fuji']),
})
})
TIP
可以将 expect.not
与此匹配器一起使用来否定期望值。
expect.objectContaining
- 类型:
(expected: any) => any
当与相等检查一起使用时,如果值的形状相似,该非对称匹配器将返回 true
。
import { expect, test } from 'vitest'
test('basket has empire apples', () => {
const basket = {
varieties: [
{
name: 'Empire',
count: 1,
},
],
}
expect(basket).toEqual({
varieties: [expect.objectContaining({ name: 'Empire' })],
})
})
TIP
可以将 expect.not
与此匹配器一起使用,以否定预期值。
expect.stringContaining
- 类型:
(expected: any) => any
当与相等性检查一起使用时,这个不对称的匹配器将在值为字符串且包含指定子字符串时返回true
。
import { expect, test } from 'vitest'
test('variety has "Emp" in its name', () => {
const variety = {
name: 'Empire',
count: 1,
}
expect(variety).toEqual({
name: expect.stringContaining('Emp'),
count: 1,
})
})
TIP
可以将 expect.not
与此匹配器一起使用,以否定预期值。
expect.stringMatching
- 类型:
(expected: any) => any
当与相等性检查一起使用时,这个不对称的匹配器将在值为字符串且包含指定子字符串,或者字符串与正则表达式匹配时返回 true
。
import { expect, test } from 'vitest'
test('variety ends with "re"', () => {
const variety = {
name: 'Empire',
count: 1,
}
expect(variety).toEqual({
name: expect.stringMatching(/re$/),
count: 1,
})
})
TIP
可以将 expect.not
与此匹配器一起使用,以否定预期值。
expect.addSnapshotSerializer
- 类型:
(plugin: PrettyFormatPlugin) => void
这个方法添加了在创建快照时调用的自定义序列化程序。这是一个高级功能 - 如果想了解更多,请阅读有关自定义序列化程序的指南。
如果需要添加自定义序列化程序,应该在 setupFiles
中调用此方法。这将影响每个快照。
TIP
如果以前将 Vue CLI 与 Jest 一起使用,需要安装 jest-serializer-vue。 否则,的快照将被包裹在一个字符串中,其中 "
是要转义的。
expect.extend
- 类型:
(matchers: MatchersObject) => void
我们可以使用自定义匹配器扩展默认匹配器。这个函数用于使用自定义匹配器扩展匹配器对象。
当我们以这种方式定义匹配器时,还会创建可以像 expect.stringContaining
一样使用的不对称匹配器。
import { expect, test } from 'vitest'
test('custom matchers', () => {
expect.extend({
toBeFoo: (received, expected) => {
if (received !== 'foo') {
return {
message: () => `expected ${received} to be foo`,
pass: false,
}
}
},
})
expect('foo').toBeFoo()
expect({ foo: 'foo' }).toEqual({ foo: expect.toBeFoo() })
})
TIP
如果希望匹配器出现在每个测试中,应该在 setupFiles
中调用此方法。
这个函数与 Jest 的 expect.extend
兼容,因此任何使用它来创建自定义匹配器的库都可以与 Vitest 一起使用。
如果正在使用 TypeScript,自从 Vitest 0.31.0 版本以来,我们可以在环境声明文件(例如:vitest.d.ts
)中使用下面的代码扩展默认的 Assertion
接口:
interface CustomMatchers<R = unknown> {
toBeFoo: () => R
}
declare module 'vitest' {
interface Assertion<T = any> extends CustomMatchers<T> {}
interface AsymmetricMatchersContaining extends CustomMatchers {}
}
WARNING
不要忘记在 tsconfig.json
中包含环境声明文件。
TIP
如果想了解更多信息,请查看 扩展断言 (Matchers) 指南。
expect.addEqualityTesters
- 类型:
(tester: Array<Tester>) => void
你可以使用此方法定义自定义测试器(匹配器使用的方法),以测试两个对象是否相等。它与 Jest 的 expect.addEqualityTesters
兼容。
import { expect, test } from 'vitest'
class AnagramComparator {
public word: string
constructor(word: string) {
this.word = word
}
equals(other: AnagramComparator): boolean {
const cleanStr1 = this.word.replace(/ /g, '').toLowerCase()
const cleanStr2 = other.word.replace(/ /g, '').toLowerCase()
const sortedStr1 = cleanStr1.split('').sort().join('')
const sortedStr2 = cleanStr2.split('').sort().join('')
return sortedStr1 === sortedStr2
}
}
function isAnagramComparator(a: unknown): a is AnagramComparator {
return a instanceof AnagramComparator
}
function areAnagramsEqual(a: unknown, b: unknown): boolean | undefined {
const isAAnagramComparator = isAnagramComparator(a)
const isBAnagramComparator = isAnagramComparator(b)
if (isAAnagramComparator && isBAnagramComparator) {
return a.equals(b)
}
else if (isAAnagramComparator === isBAnagramComparator) {
return undefined
}
else {
return false
}
}
expect.addEqualityTesters([areAnagramsEqual])
test('custom equality tester', () => {
expect(new AnagramComparator('listen')).toEqual(
new AnagramComparator('silent')
)
})