expect
以下类型在以下类型签名中使用:
type Awaitable<T> = T | PromiseLike<T>;
expect
用于创建断言。在此上下文中,断言
是指可以被调用的函数,用于断言某个语句是否为真。Vitest 默认提供 chai
断言,并且还提供基于 chai
构建的、与 Jest
兼容的断言。
例如,以下代码断言 input
的值等于 2
。如果不相等,断言将抛出错误,测试将失败。
import { expect } from 'vitest';
const input = Math.sqrt(4);
expect(input).to.equal(2); // chai API
expect(input).toBe(2); // jest API
严格来说,此示例没有使用 test
函数,因此在控制台中将看到 Node.js 错误,而不是 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); // 标记测试为失败状态并继续执行
expect.soft(1 + 2).toBe(4); // 标记测试为失败状态并继续执行
});
// 在测试结束时,将输出以上错误。
它也可以与 expect
一起使用。如果 expect
断言失败,测试将终止,并显示所有错误。
import { expect, test } from 'vitest';
test('expect.soft test', () => {
expect.soft(1 + 1).toBe(3); // 标记测试为失败状态并继续执行
expect(1 + 2).toBe(4); // 失败并终止测试,将输出所有先前的错误
expect.soft(1 + 3).toBe(5); // 不运行
});
WARNING
expect.soft
只能在 test
函数内部使用。
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', () => {
// 小数点后保留 5 位有效数字
// 0.2 + 0.1 结果为 0.30000,移除了 "000000000004"
expect(0.2 + 0.1).toBeCloseTo(0.3, 5);
// 0.30000000000000004 没有被移除任何内容
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
断言该值为 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();
});
JavaScript 中除了 false
, null
, undefined
, NaN
, 0
, -0
, 0n
, ""
和 document.all
之外,所有内容都是真值。
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();
});
JavaScript 中除了 false
, null
, undefined
, NaN
, 0
, -0
, 0n
, ""
和 document.all
之外,所有内容都是真值。
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
对象,不会执行深度相等比较。只有 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('水果列表包含 orange', () => {
expect(getAllFruits()).toContain('orange');
const element = document.querySelector('#el');
// element 有一个 class
expect(element.classList).toContain('flex');
// element 在另一个 element 内部
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); // 字符串没有长度为 3 的 .length 属性
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'); // 断言键存在
expect(invoice).toHaveProperty('total_amount', 5000); // 断言键存在且值相等
expect(invoice).not.toHaveProperty('account'); // 断言键不存在
// 使用点符号进行深层引用
expect(invoice).toHaveProperty('customer.first_name');
expect(invoice).toHaveProperty('customer.last_name', 'Doe');
expect(invoice).not.toHaveProperty('customer.location', 'India');
// 使用包含键的数组进行深层引用
expect(invoice).toHaveProperty('items[0].type', 'apples');
expect(invoice).toHaveProperty('items.0.type', 'apples'); // 点符号也有效
// 使用包含 keyPath 的数组进行深层引用
expect(invoice).toHaveProperty(['items', 0, 'type'], 'apples');
expect(invoice).toHaveProperty(['items', '0', 'type'], 'apples'); // 字符串符号也有效
// 将你的 key 包装在数组中,以避免该键被解析为深层引用
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 也接受字符串
});
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', () => {
// 断言对象的数组匹配
expect([{ foo: 'bar' }, { baz: 1 }]).toMatchObject([
{ foo: 'bar' },
{ baz: 1 },
]);
});
toThrowError
类型:
(received: any) => Awaitable<void>
别名:
toThrow
toThrowError
断言函数在调用时是否抛出异常。
你可以提供一个可选参数来测试是否抛出了特定的错误:
- 正则表达式:错误消息与该模式匹配
- 字符串:错误消息包含该子字符串
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('测试在菠萝上抛出错误', () => {
// 测试错误消息中包含 "stock":以下是等效的
expect(() => getFruitStock('pineapples')).toThrowError(/stock/);
expect(() => getFruitStock('pineapples')).toThrowError('stock');
// 测试确切的错误消息
expect(() => getFruitStock('pineapples')).toThrowError(
/^Pineapples are not in stock$/
);
});
TIP
要测试异步函数,请与 rejects 结合使用。
function getAsyncFruitStock() {
return Promise.reject(new Error('empty'));
}
test('测试在菠萝上抛出错误', 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) });
});
toMatchInlineSnapshot
- 类型:
<T>(shape?: Partial<T> | string, snapshot?: string, message?: string) => void
这确保值与最新快照匹配。
Vitest 会将 inlineSnapshot
字符串参数添加到测试文件的匹配器中(而不是外部的 .snap
文件),并自动更新该参数。
import { expect, test } from 'vitest';
test('matches inline snapshot', () => {
const data = { foo: new Set(['bar', 'snapshot']) };
// Vitest 将在更新快照时更新以下内容
expect(data).toMatchInlineSnapshot(`
{
"foo": Set {
"bar",
"snapshot",
},
}
`);
});
如果只需要测试对象的结构,而不需要完全匹配,可以提供一个对象结构作为参数:
import { expect, test } from 'vitest';
test('matches snapshot', () => {
const data = { foo: new Set(['bar', 'snapshot']) };
expect(data).toMatchInlineSnapshot(
{ foo: expect.any(Set) },
`
{
"foo": Any<Set>,
}
`
);
});
toMatchFileSnapshot 0.30.0+
- 类型:
<T>(filepath: string, message?: string) => Promise<void>
将快照与明确指定的文件内容进行比较或更新(而不是默认的 .snap
文件)。
import { expect, it } from 'vitest';
it('render basic', async () => {
const result = renderHTML(h('div', { class: 'foo' }));
await expect(result).toMatchFileSnapshot('./test/basic.output.html');
});
请注意,由于文件系统操作是异步的,你需要将 await
与 toMatchFileSnapshot()
一起使用。
toThrowErrorMatchingSnapshot
- 类型:
(message?: string) => void
与 toMatchSnapshot
相同,但期望的值与 toThrowError
相同。
toThrowErrorMatchingInlineSnapshot
- 类型:
(snapshot?: string, message?: string) => void
与 toMatchInlineSnapshot
相同,但期望的值与 toThrowError
相同。
toHaveBeenCalled
- 类型:
() => Awaitable<void>
此断言用于测试函数是否被调用过。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
const market = {
buy(subject: string, amount: number) {
// ...
},
};
test('spy function', () => {
const buySpy = vi.spyOn(market, 'buy');
expect(buySpy).not.toHaveBeenCalled();
market.buy('apples', 10);
expect(buySpy).toHaveBeenCalled();
});
toHaveBeenCalledTimes
- 类型:
(amount: number) => Awaitable<void>
此断言用于检查函数被调用的次数。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
const market = {
buy(subject: string, amount: number) {
// ...
},
};
test('spy function called two times', () => {
const buySpy = vi.spyOn(market, 'buy');
market.buy('apples', 10);
market.buy('apples', 20);
expect(buySpy).toHaveBeenCalledTimes(2);
});
toHaveBeenCalledWith
- 类型:
(...args: any[]) => Awaitable<void>
此断言用于检查函数是否至少被调用一次,且调用时使用了指定的参数。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
const market = {
buy(subject: string, amount: number) {
// ...
},
};
test('spy function', () => {
const buySpy = vi.spyOn(market, 'buy');
market.buy('apples', 10);
market.buy('apples', 20);
expect(buySpy).toHaveBeenCalledWith('apples', 10);
expect(buySpy).toHaveBeenCalledWith('apples', 20);
});
toHaveBeenLastCalledWith
- 类型:
(...args: any[]) => Awaitable<void>
此断言用于检查函数最后一次调用时是否使用了指定的参数。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
const market = {
buy(subject: string, amount: number) {
// ...
},
};
test('spy function', () => {
const buySpy = vi.spyOn(market, 'buy');
market.buy('apples', 10);
market.buy('apples', 20);
expect(buySpy).not.toHaveBeenLastCalledWith('apples', 10);
expect(buySpy).toHaveBeenLastCalledWith('apples', 20);
});
toHaveBeenNthCalledWith
- 类型:
(time: number, ...args: any[]) => Awaitable<void>
此断言用于检查函数在第 N 次调用时是否使用了指定的参数。
计数从 1 开始。例如,要检查第二次调用,应使用 .toHaveBeenNthCalledWith(2, ...)
。
需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
const market = {
buy(subject: string, amount: number) {
// ...
},
};
test('first call of spy function called with right params', () => {
const buySpy = vi.spyOn(market, 'buy');
market.buy('apples', 10);
market.buy('apples', 20);
expect(buySpy).toHaveBeenNthCalledWith(1, 'apples', 10);
});
toHaveReturned
- 类型:
() => Awaitable<void>
此断言用于检查函数是否成功返回过值(即没有抛出错误)。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
function getApplesPrice(amount: number) {
const PRICE = 10;
return amount * PRICE;
}
test('spy function returned a value', () => {
const getPriceSpy = vi.fn(getApplesPrice);
const price = getPriceSpy(10);
expect(price).toBe(100);
expect(getPriceSpy).toHaveReturned();
});
toHaveReturnedTimes
- 类型:
(amount: number) => Awaitable<void>
此断言用于检查函数是否成功返回了指定次数的值(即没有抛出错误)。需要将 spy 函数传递给 expect
。
import { expect, test, vi } from 'vitest';
test('spy function returns a value two times', () => {
const sell = vi.fn((product: string) => ({ product }));
sell('apples');
sell('bananas');
expect(sell).toHaveReturnedTimes(2);
});
toHaveReturnedWith
- 类型:
(returnValue: any) => Awaitable<void>
你可以调用此断言来检查一个函数是否至少成功返回过一次特定的值。需要将一个监视函数(spy 函数)传递给 expect
。
import { expect, test, vi } from 'vitest';
test('spy function returns a product', () => {
const sell = vi.fn((product: string) => ({ product }));
sell('apples');
expect(sell).toHaveReturnedWith({ product: 'apples' });
});
toHaveLastReturnedWith
- 类型:
(returnValue: any) => Awaitable<void>
你可以调用此断言来检查一个函数是否在其最后一次调用时成功返回了特定的值。需要将一个监视函数(spy 函数)传递给 expect
。
import { expect, test, vi } from 'vitest';
test('spy function returns bananas on a last call', () => {
const sell = vi.fn((product: string) => ({ product }));
sell('apples');
sell('bananas');
expect(sell).toHaveLastReturnedWith({ product: 'bananas' });
});
toHaveNthReturnedWith
- 类型:
(time: number, returnValue: any) => Awaitable<void>
你可以调用此断言来检查一个函数是否在特定调用时成功返回了特定的值。需要将一个监视函数(spy 函数)传递给 expect
。
import { expect, test, vi } from 'vitest';
test('spy function returns bananas on second call', () => {
const sell = vi.fn((product: string) => ({ product }));
sell('apples');
sell('bananas');
expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' });
});
toSatisfy
- 类型:
(predicate: (value: any) => boolean) => Awaitable<void>
此断言检查一个值是否满足某个谓词函数。
import { describe, expect, it } from 'vitest';
describe('toSatisfy()', () => {
const isOdd = (value: number) => value % 2 !== 0;
it('pass with 0', () => {
expect(1).toSatisfy(isOdd);
});
it('pass with negotiation', () => {
expect(2).not.toSatisfy(isOdd);
});
});
resolves
- 类型:
Promisify<Assertions>
resolves
旨在简化断言异步代码。使用它可以提取 Promise 成功 resolve 后的值,并使用常规的断言方式来断言该值。如果 Promise 被 rejected,断言将会失败。
它返回相同的 Assertions
对象,但所有 matcher(匹配器)现在都返回 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 现在返回一个 Promise,所以你必须 await 它
await expect(buyApples()).resolves.toEqual({ id: 1 }); // jest API
await expect(buyApples()).resolves.to.equal({ id: 1 }); // chai API
});
WARNING
如果未 await
断言,测试将始终通过,导致假阳性。为了确保断言实际上被调用,你可以使用 expect.assertions(number)
。
rejects
- 类型:
Promisify<Assertions>
rejects
旨在简化断言异步代码。使用它可以提取 Promise 被 rejected 的原因,并使用常规的断言方式来断言该值。如果 Promise 成功 resolve,断言将会失败。
它返回相同的 Assertions
对象,但所有 matcher(匹配器)现在都返回 Promise
,因此你需要 await
它。也适用于 chai
断言。
例如,如果你有一个函数,当你调用它时会失败,你可以使用以下代码来断言 rejected 的原因:
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 现在返回一个 Promise,所以你必须 await 它
await expect(buyApples()).rejects.toThrow('no id');
});
WARNING
如果未 await
断言,测试将始终通过,导致假阳性。为了确保断言实际上被调用,你可以使用 expect.assertions(number)
。
expect.assertions
- 类型:
(count: number) => void
在测试结束后,验证是否调用了特定数量的断言。这在检查异步代码是否被调用时非常有用。
例如,如果我们有一个异步调用两个 matcher(匹配器)的函数,我们可以断言它们实际上被调用了。
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
当将 assertions
与异步并发测试一起使用时,必须使用来自本地 测试环境 的 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 => {
expect(data).toBeTruthy();
});
// if not awaited, test will fail
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 1.0.0+
- 类型:
(expected: any, precision?: number) => any
当比较对象属性或数组项中的浮点数时,expect.closeTo
非常有用。 如果需要比较一个数字,请改用 .toBeCloseTo
。
可选的 numDigits
参数限制了要检查的小数点后的位数。 对于默认值 2
,测试标准是 Math.abs(expected - received) < 0.005 (即,10 ** -2 / 2)
。
例如,此测试以 5 位精度通过:
test('比较对象属性中的浮点数', () => {
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
与此 matcher(匹配器)一起使用,以取反期望值,即数组不包含指定项。
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
与此 matcher(匹配器)一起使用,以取反期望值,即对象不包含指定的属性。
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
与此 matcher(匹配器)一起使用,以取反期望值,即字符串不包含指定的子字符串。
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
与此 matcher(匹配器)一起使用,以取反期望值,即字符串不匹配指定的正则表达式。
expect.addSnapshotSerializer
- 类型:
(plugin: PrettyFormatPlugin) => void
此方法添加自定义序列化插件,用于创建快照时调用。这是一个高级功能 - 如果你想了解更多信息,请阅读 自定义序列化器指南。
添加自定义序列化插件时,应在 setupFiles
中调用此方法。这将影响每个快照。
TIP
如果你之前使用过带有 Jest 的 Vue CLI,你可能需要安装 jest-serializer-vue。否则,你的快照将被包装在一个字符串中,这会导致 "
被转义。
expect.extend
- 类型:
(matchers: MatchersObject) => void
你可以使用自定义匹配器扩展默认匹配器。此函数用于使用自定义 matcher 扩展 matcher 对象。
当你以这种方式定义 matcher 时,你还会创建可以像 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
如果你希望你的 matcher 在每个测试中都可用,则应在 setupFiles
中调用此方法。
此函数与 Jest 的 expect.extend
兼容,因此任何使用它来创建自定义 matcher 的库都将与 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
如果你想了解更多信息,请查看 扩展 matcher 指南。
expect.addEqualityTesters 1.2.0+
- 类型:
(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 = isBAnagramComparator(b);
if (isAAnagramComparator && isBAnagramComparator) return a.equals(b);
else if (isAAnagramComparator === isBAnagramComparator) return undefined;
else return false;
}
expect.addEqualityTesters([areAnagramsEqual]);
test('自定义相等性测试器', () => {
expect(new AnagramComparator('listen')).toEqual(
new AnagramComparator('silent')
);
});