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
。如果不是,斷言將拋出錯誤,測試將失敗。
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
函式內部使用。
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
。如果相等,斷言將拋出錯誤,測試將失敗。
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; // 相同的引用
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 是 0.30000000000000004
});
test('decimals are rounded to 5 after the point', () => {
// 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 沒有庫存', () => {
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('我們沒有蘋果', () => {
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('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
物件,也會比較不可列舉的屬性,例如 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('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
也可以檢查一個字串是否為另一個字串的子字串。如果您在類似瀏覽器的環境中執行測試,此斷言還可以檢查類別是否包含在 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 會將 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",
},
}
`);
});
您也可以提供物件的形狀,如果您只是測試物件的形狀,並且不需要它 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');
});
請注意,由於檔案系統操作是異步的,您需要將 await
與 toMatchFileSnapshot()
一起使用。如果未使用 await
,Vitest 會將其視為 expect.soft
,這表示即使快照不相符,語句後的程式碼也會繼續執行。測試完成後,Vitest 將檢查快照,如果存在不相符,則會失敗。
toThrowErrorMatchingSnapshot
- 類型:
(hint?: string) => void
與 toMatchSnapshot
相同,但預期與 toThrowError
相同的值。
toThrowErrorMatchingInlineSnapshot
- 類型:
(snapshot?: string, hint?: string) => void
與 toMatchInlineSnapshot
相同,但預期與 toThrowError
相同的值。
toHaveBeenCalled
- 類型:
() => Awaitable<void>
此斷言用於測試函式是否已被呼叫。需要將間諜函式傳遞給 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>
此斷言檢查函式是否被呼叫了特定的次數。需要將間諜函式傳遞給 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>
此斷言檢查函式是否至少被呼叫過一次,並帶有特定的參數。需要將間諜函式傳遞給 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>
此斷言檢查函式是否只被呼叫一次,並帶有特定的參數。需要將間諜函式傳遞給 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>
此斷言檢查函式在上次呼叫時是否帶有特定的參數。需要將間諜函式傳遞給 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, ...)
。
需要將間諜函式傳遞給 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>
此斷言檢查函式是否至少成功返回一次值(即,沒有拋出錯誤)。需要將間諜函式傳遞給 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>
此斷言檢查函式是否成功返回了確切次數的值(即,沒有拋出錯誤)。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式是否至少成功返回一次帶有特定參數的值。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式在上次呼叫時是否成功返回了特定值。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式是否在特定呼叫時成功返回了帶有特定參數的值。需要將間諜函式傳遞給 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>
此斷言檢查函式是否至少成功解析一次值(即,沒有拒絕)。需要將間諜函式傳遞給 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>
此斷言檢查函式是否成功解析了確切次數的值(即,沒有拒絕)。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式是否至少成功解析一次特定值。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式在上次呼叫時是否成功解析了特定值。需要將間諜函式傳遞給 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>
您可以呼叫此斷言來檢查函式是否在特定呼叫時成功解析了特定值。需要將間諜函式傳遞給 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 它
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 它
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
與非同步並行測試一起使用時,必須使用來自本地 測試上下文 的 expect
,以確保測試正確執行。
expect.hasAssertions
- 類型:
() => void
在測試通過或失敗後,驗證在測試期間至少呼叫了一個斷言。一個有用的應用場景是檢查是否呼叫了非同步程式碼。
例如,如果您有一段呼叫回呼的程式碼,我們可以在回呼內部進行斷言,但如果我們不檢查斷言是否被呼叫,測試將始終通過。
import { expect, test } from 'vitest';
import { db } from './db.js';
const cbs = [];
function onSelect(cb) {
cbs.push(cb);
}
// 從 db 選擇後,我們呼叫所有回呼
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('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:
// 窮盡所有錯誤測試
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 (即 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
如果您想了解更多,請查看擴展匹配器指南。
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')
);
});