expect
以下类型在下面的类型签名中被使用。
type Awaitable<T> = T | PromiseLike<T>;expect 用于创建断言。在此上下文中,断言是可调用的函数,用于验证某个语句的正确性。Vitest 默认提供 chai 断言,以及基于 chai 构建的 Jest 兼容断言。与 Jest 不同,Vitest 支持将错误消息作为第二个参数——如果断言失败,将显示该错误消息。
export interface ExpectStatic
extends Chai.ExpectStatic,
AsymmetricMatchersContaining {
<T>(actual: T, message?: string): Assertion<T>;
extend: (expects: MatchersObject) => void;
anything: () => any;
any: (constructor: unknown) => any;
getState: () => MatcherState;
setState: (state: Partial<MatcherState>) => void;
not: AsymmetricMatchersContaining;
}例如,此代码断言 input 值等于 2。如果不等于 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 的内容,请阅读 Test API Reference。
此外,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 函数内部使用。
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 使每个断言都成为异步操作,因此你需要 await 它。从 Vitest 3 开始,如果你忘记 await 它,测试将失败并提示警告。
expect.poll 不适用于以下几个匹配器:
- 快照匹配器不受支持,因为它们总是会成功。如果你的条件不稳定,请考虑先使用
vi.waitFor来解决它:
import { expect, vi } from 'vitest';
const flakyValue = await vi.waitFor(() => getFlakyValue());
expect(flakyValue).toMatchSnapshot();- 不支持
.resolves和.rejects。如果条件是异步的,expect.poll已经对其进行了await。 toThrow及其别名不受支持,因为expect.poll条件总是在匹配器获取值之前解析。
not
使用 not 将对断言进行否定。例如,此代码断言 input 值不等于 2。如果它等于 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 APItoBe
- 类型:
(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; // 相同的引用
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('JavaScript 中的小数不相等', () => {
expect(0.2 + 0.1).toBe(0.3); // 0.2 + 0.1 是 0.30000000000000004
});
test('小数点后保留 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('玛丽没有库存', () => {
expect(getApplesFromStock('Mary')).toBeUndefined();
});toBeTruthy
- 类型:
() => Awaitable<void>
toBeTruthy 断言值在转换为布尔值时应为 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('如果知道 Bill 的库存,就卖苹果给他', () => {
stocks.sync('Bill');
expect(stocks.getInfo('Bill')).toBeTruthy();
});在 JavaScript 中,除了 false、null、undefined、NaN、0、-0、0n、"" 和 document.all 之外,所有值都是真值。
toBeFalsy
- 类型:
() => Awaitable<void>
toBeFalsy 断言值在转换为布尔值时应为 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('如果 Bill 的库存没有失败,就卖苹果给他', () => {
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('我们没有苹果', () => {
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 有一些不寻常的副作用...', () => {
expect(getApplesCount()).not.toBeNaN();
expect(getApplesCount()).toBeNaN();
});toBeOneOf
- 类型:
(sample: Array<any>) => any
toBeOneOf 用于断言一个值是否与提供的数组中的任意值匹配。
import { expect, test } from 'vitest';
test('fruit is one of the allowed values', () => {
expect(fruit).toBeOneOf(['apple', 'banana', 'orange']);
});这种非对称匹配器在测试可选属性(可以是 null 或 undefined)时特别有用:
test('optional properties can be null or undefined', () => {
const user = {
firstName: 'John',
middleName: undefined,
lastName: 'Doe',
};
expect(user).toEqual({
firstName: expect.any(String),
middleName: expect.toBeOneOf([expect.any(String), undefined]),
lastName: expect.any(String),
});
});TIP
你可以将 expect.not 与此匹配器一起使用,以确保值不匹配任何提供的选项。
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('有超过 10 个苹果', () => {
expect(getApples()).toBeGreaterThan(10);
});toBeGreaterThanOrEqual
- 类型:
(n: number | bigint) => Awaitable<void>
toBeGreaterThanOrEqual 断言实际值是否大于或等于接收到的值。
import { expect, test } from 'vitest';
import { getApples } from './stocks.js';
test('有 11 个或更多苹果', () => {
expect(getApples()).toBeGreaterThanOrEqual(11);
});toBeLessThan
- 类型:
(n: number | bigint) => Awaitable<void>
toBeLessThan 断言实际值是否小于接收到的值。相等的值会导致测试失败。
import { expect, test } from 'vitest';
import { getApples } from './stocks.js';
test('有少于 20 个苹果', () => {
expect(getApples()).toBeLessThan(20);
});toBeLessThanOrEqual
- 类型:
(n: number | bigint) => Awaitable<void>
toBeLessThanOrEqual 断言实际值是否小于或等于接收到的值。
import { expect, test } from 'vitest';
import { getApples } from './stocks.js';
test('有 11 个或更少苹果', () => {
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 对象,非枚举属性(如 name、message、cause 和 AggregateError.errors)也会被比较。对于 Error.cause,比较是非对称进行的:
// 成功
expect(new Error('hi', { cause: 'x' })).toEqual(new Error('hi'));
// 失败
expect(new Error('hi')).toEqual(new Error('hi', { cause: 'x' }));要测试是否抛出了某些内容,请使用 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('结构相同,但语义不同', () => {
expect(new Stock('apples')).toEqual({ type: 'apples' });
expect(new Stock('apples')).not.toStrictEqual({ type: 'apples' });
});toContain
- 类型:
(received: string) => Awaitable<void>
toContain 断言实际值是否包含在数组中。toContain 还可以检查一个字符串是否是另一个字符串的子字符串。如果你在类似浏览器的环境中运行测试,此断言还可以检查 class 是否包含在 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');
// 元素有一个类
expect(element.classList).toContain('flex');
// 元素在另一个元素内部
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); // 没有 .length 为 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'); // 断言该键存在
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'); // 点表示法也适用
// 使用包含键路径的数组进行深度引用
expect(invoice).toHaveProperty(['items', 0, 'type'], 'apples');
expect(invoice).toHaveProperty(['items', '0', 'type'], 'apples'); // 字符串表示法也适用
// 将键包装在数组中,以避免键被解析为深度引用
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 用于断言函数在调用时是否抛出错误。
你可以提供一个可选参数来测试是否抛出了特定的错误:
RegExp: 错误消息匹配该模式string: 错误消息包含该子字符串Error,AsymmetricMatcher: 与接收到的对象进行比较,类似于toEqual(received)
TIP
必须将待测试代码包装在函数中,否则错误无法被捕获,导致测试失败。
这不适用于异步调用,因为 rejects 会正确地解析 Promise:
test('expect rejects toThrow', async ({ expect }) => {
const promise = Promise.reject(new Error('Test'));
await expect(promise).rejects.toThrowError();
});例如,如果我们想测试 getFruitStock('pineapples') 是否抛出错误,我们可以这样写:
import { expect, test } from 'vitest';
function getFruitStock(type: string) {
if (type === 'pineapples') {
throw new Error('Pineapples are not in stock');
}
// 执行其他操作
}
test('throws on pineapples', () => {
// 测试错误消息中是否包含 "stock":以下两种写法等效
expect(() => getFruitStock('pineapples')).toThrowError(/stock/);
expect(() => getFruitStock('pineapples')).toThrowError('stock');
// 测试精确的错误消息
expect(() => getFruitStock('pineapples')).toThrowError(
/^Pineapples are not in stock$/
);
expect(() => getFruitStock('pineapples')).toThrowError(
new Error('Pineapples are not in stock')
);
expect(() => getFruitStock('pineapples')).toThrowError(
expect.objectContaining({
message: '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, hint?: string) => void
这用于确保一个值与最新的快照匹配。
你可以提供一个可选的 hint 字符串参数,该参数会附加到测试名称后面。尽管 Vitest 总是会在快照名称末尾附加一个数字,但简短的描述性提示可能比数字更有用,以便在单个 it 或 test 块中区分多个快照。Vitest 会根据名称在对应的 .snap 文件中对快照进行排序。
TIP
当快照不匹配并导致测试失败时,如果这种不匹配是预期的,你可以按 u 键来更新快照。或者你可以传递 -u 或 --update CLI 选项,让 Vitest 总是更新测试。
import { expect, test } from 'vitest';
test('matches snapshot', () => {
const data = { foo: new Set(['bar', 'snapshot']) };
expect(data).toMatchSnapshot();
});你也可以提供一个对象的形状,如果你只是测试对象的形状,并且不需要它 100% 兼容:
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, hint?: string) => void
这用于确保一个值与最新的快照匹配。
Vitest 会在测试文件中添加和更新内联快照字符串作为匹配器的参数(而不是外部的 .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",
},
}
`);
});你也可以提供一个对象的形状,如果你只是测试对象的形状,并且不需要它 100% 兼容:
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
- 类型:
<T>(filepath: string, hint?: 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');
});请注意,由于文件系统操作是异步的,你需要对 toMatchFileSnapshot() 使用 await。如果未使用 await,Vitest 会将其视为 expect.soft,这意味着即使快照不匹配,语句后面的代码也会继续运行。测试完成后,Vitest 将检查快照,如果存在不匹配,则会失败。
toThrowErrorMatchingSnapshot
- 类型:
(hint?: string) => void
与 toMatchSnapshot 相同,但期望的值与 toThrowError 相同。
toThrowErrorMatchingInlineSnapshot
- 类型:
(snapshot?: string, hint?: 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);
});toHaveBeenCalledBefore 3.0.0+
- 类型:
(mock: MockInstance, failIfNoFirstInvocation?: boolean) => Awaitable<void>
此断言检查一个 Mock 是否在另一个 Mock 之前被调用。
test('calls mock1 before mock2', () => {
const mock1 = vi.fn();
const mock2 = vi.fn();
mock1();
mock2();
mock1();
expect(mock1).toHaveBeenCalledBefore(mock2);
});toHaveBeenCalledAfter 3.0.0+
- 类型:
(mock: MockInstance, failIfNoFirstInvocation?: boolean) => Awaitable<void>
此断言检查一个 Mock 是否在另一个 Mock 之后被调用。
test('calls mock1 after mock2', () => {
const mock1 = vi.fn();
const mock2 = vi.fn();
mock2();
mock1();
mock2();
expect(mock1).toHaveBeenCalledAfter(mock2);
});toHaveBeenCalledExactlyOnceWith 3.0.0+
- 类型:
(...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);
expect(buySpy).toHaveBeenCalledExactlyOnceWith('apples', 10);
});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>
此断言检查函数在特定时间是否带有某些参数被调用。计数从 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' });
});toHaveResolved
- 类型:
() => Awaitable<void>
此断言检查函数是否至少成功解析了一个值(即没有拒绝)。需要将一个 spy 函数传递给 expect。
如果函数返回了一个 Promise,但尚未解析,则此断言会失败。
import { expect, test, vi } from 'vitest';
import db from './db/apples.js';
async function getApplesPrice(amount: number) {
return amount * (await db.get('price'));
}
test('spy function resolved a value', async () => {
const getPriceSpy = vi.fn(getApplesPrice);
const price = await getPriceSpy(10);
expect(price).toBe(100);
expect(getPriceSpy).toHaveResolved();
});toHaveResolvedTimes
- 类型:
(amount: number) => Awaitable<void>
此断言检查函数是否成功解析了精确次数的值(即没有拒绝)。需要将一个 spy 函数传递给 expect。
这只会计算已解析的 Promise。如果函数返回了一个 Promise,但尚未解析,则不会被计数。
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,但尚未解析,则此断言会失败。
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
- 类型:
(returnValue: any) => Awaitable<void>
你可以调用此断言来检查函数在最后一次调用时是否成功解析了某个值。需要将一个 spy 函数传递给 expect。
如果函数返回了一个 Promise,但尚未解析,则此断言会失败。
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
- 类型:
(time: number, returnValue: any) => Awaitable<void>
你可以调用此断言来检查函数在特定调用时是否成功解析了某个值。需要将一个 spy 函数传递给 expect。
如果函数返回了一个 Promise,但尚未解析,则此断言会失败。
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>
此断言检查一个值是否满足某个谓词。
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 现在返回一个 Promise,所以你必须等待它完成
await expect(buyApples()).resolves.toEqual({ id: 1 }); // jest API
await expect(buyApples()).resolves.to.equal({ id: 1 }); // chai API
});WARNING
如果断言没有被 await,那么你将得到一个假阳性测试,它每次都会通过。为了确保断言确实被调用,你可以使用 expect.assertions(number)。
从 Vitest 3 开始,如果一个方法没有被 await,Vitest 将在测试结束时显示警告。在 Vitest 4 中,如果断言没有被 await,测试将被标记为“失败”。
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 现在返回一个 Promise,所以你必须等待它完成
await expect(buyApples()).rejects.toThrow('no id');
});WARNING
如果断言没有被 await,那么你将得到一个假阳性测试,它每次都会通过。为了确保断言确实被调用,你可以使用 expect.assertions(number)。
从 Vitest 3 开始,如果一个方法没有被 await,Vitest 将在测试结束时显示警告。在 Vitest 4 中,如果断言没有被 await,测试将被标记为“失败”。
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
当在异步并发测试中使用 assertions 时,必须使用本地 Test Context 中的 expect 来确保检测到正确的测试。
expect.hasAssertions
- 类型:
() => void
在测试通过或失败后,验证在测试期间是否至少调用了一个断言。一个有用的用例是检查异步代码是否被调用。
例如,如果你有一段代码会调用回调函数,我们可以在回调函数内部进行断言,但如果我们不检查断言是否被调用,测试将始终通过。
import { expect, test } from 'vitest';
import { db } from './db.js';
const cbs = [];
function onSelect(cb) {
cbs.push(cb);
}
// 从数据库选择后,我们调用所有回调
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();
});
// 如果没有 await,测试将失败
// 如果你没有 expect.hasAssertions(),测试将通过
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('不应该通过构建');
} 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:
// 穷尽所有错误测试
expect.unreachable('所有错误测试都必须被处理');
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 (即 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 与此匹配器一起使用,以否定预期值。
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
如果你想了解更多,请查看 扩展匹配器指南。
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')
);
});