expect
以下類型用於下面的類型簽名中
type Awaitable<T> = T | PromiseLike<T>;
expect
用於建立斷言。在此情境下,斷言(assertions)
是可以呼叫來驗證陳述的函式。 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
可以靜態使用,以存取匹配器(matchers)函式(稍後描述)以及更多功能。
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', () => {
// 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
斷言該值是否等於 undefined
。一個常見的用途是檢查函式是否尚未回傳任何值。
import { expect, test } from 'vitest';
function getApplesFromStock(stock) {
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); // doesn't have .length of 3
expect({ length: 3 }).toHaveLength(3);
});
toHaveProperty
- 類型:
(key: any, received?: any) => Awaitable<void>
toHaveProperty
斷言物件是否具有指定的 key
屬性。
您可以提供一個可選的值參數,進行深層相等比較(deep equality),如同 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'); // 斷言該 key 存在
expect(invoice).toHaveProperty('total_amount', 5000); // 斷言 key 存在且值相等
expect(invoice).not.toHaveProperty('account'); // 斷言該 key 不存在
// 使用點記法進行深層參考
expect(invoice).toHaveProperty('customer.first_name');
expect(invoice).toHaveProperty('customer.last_name', 'Doe');
expect(invoice).not.toHaveProperty('customer.location', 'India');
// 使用包含 key 的陣列進行深層參考
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 用陣列包裹,以避免該 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');
// 執行其他操作
}
test('throws on pineapples', () => {
// 測試錯誤訊息是否包含 '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('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
CLI 選項,使 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>
此斷言檢查函數是否在特定次數的呼叫時,使用了指定的參數。 呼叫次數從 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>
您可以呼叫此斷言來檢查函數是否至少成功回傳過一次具有特定參數的值。 需要將間諜函數傳遞給 expect
。
import { expect, test, vi } from 'vitest';
test('間諜函數回傳一個產品', () => {
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('間諜函數在最後一次呼叫時回傳香蕉', () => {
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('間諜函數在第二次呼叫時回傳香蕉', () => {
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('通過 0', () => {
expect(1).toSatisfy(isOdd);
});
it('否定測試通過', () => {
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 回傳新的庫存 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
如果未等待斷言,那麼您將會有一個假陽性測試,該測試每次都會通過。 為了確保實際呼叫斷言,您可以使用 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 在未提供 id 時拋出錯誤', async () => {
// toThrow 現在會回傳一個 Promise,所以你必須使用 await 等待它完成
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('所有斷言都被呼叫', 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('回呼函數被呼叫', async () => {
expect.hasAssertions();
onSelect(data => {
// 應該在 select 上呼叫
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('物件具有 "apples" 鍵', () => {
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" 是一個數字', () => {
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('籃子裡包含富士蘋果', () => {
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('籃子裡有帝國蘋果', () => {
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('品種名稱中含有 "Emp"', () => {
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('品種名稱以 "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
如果您之前使用過帶有 Jest 的 Vue CLI,您可能需要安裝 jest-serializer-vue。 否則,您的快照檔案將被包裝在一個字串中,這會導致 "
被跳脫。
expect.extend
- 類型:
(matchers: MatchersObject) => void
您可以使用自己的匹配器擴充預設匹配器。 此函數用於使用自訂匹配器擴充匹配器物件。
當您以這種方式定義匹配器時,您還會建立可以像 expect.stringContaining
一樣使用的非對稱匹配器。
import { expect, test } from 'vitest';
test('自訂匹配器', () => {
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 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')
);
});