Skip to content
Vitest 2
Main Navigation 指南API配置瀏覽器模式高級
2.1.9
1.6.1
0.34.6

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

繁體中文

English
简体中文
Español
Français
Русский
Português – Brasil
Deutsch
日本語
한국어
Italiano
Polski
Türkçe
čeština
magyar

外觀

Sidebar Navigation

測試 API 參考文件

模擬函數

Vi

expect

expectTypeOf

assert

assertType

本頁導覽

expect ​

以下類型用於下面的類型簽名中

ts
type Awaitable<T> = T | PromiseLike<T>;

expect 用於建立斷言。在此情境下,斷言(assertions)是可以呼叫來驗證陳述的函式。 Vitest 預設提供 chai 斷言,並提供基於 chai 建構、與 Jest 相容的斷言。

例如,以下程式碼斷言 input 值等於 2。如果不相等,則斷言將拋出錯誤,測試將失敗。

ts
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 相似,但斷言失敗時不會終止測試,而是會繼續執行,並將該失敗標記為測試失敗。測試期間發生的所有錯誤都會顯示,直到測試結束。

ts
import { expect, test } from 'vitest';

test('expect.soft test', () => {
  expect.soft(1 + 1).toBe(3); // 標記測試為失敗並繼續執行
  expect.soft(1 + 2).toBe(4); // 標記測試為失敗並繼續執行
});
// 測試運行結束時,報告器將報告這兩個錯誤

它也能與 expect 搭配使用。如果 expect 斷言失敗,測試會終止,並顯示所有錯誤。

ts
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 ​

ts
interface ExpectPoll extends ExpectStatic {
  (actual: () => T, options: { interval; timeout; message }): Promise<
    Assertions<T>
  >;
}

expect.poll 會重複運行斷言,直到成功為止。你可以透過設定 interval 和 timeout 選項來設定 Vitest 應該重複執行 expect.poll 回呼函數的次數。

如果在 expect.poll 回呼函數內部拋出錯誤,Vitest 將會再次重試,直到逾時為止。

ts
import { expect, test } from 'vitest';

test('element exists', async () => {
  asyncInjectElement();

  await expect.poll(() => document.querySelector('.element')).toBeTruthy();
});

WARNING

expect.poll 會讓每個斷言都變成非同步的,所以你需要使用 await。從 Vitest 2.2 開始,如果你忘記使用 await,測試會因為警告而失敗。

expect.poll 不支援以下幾個匹配器:

  • 不支援快照 (Snapshot) 匹配器,因為它們總是會成功。如果你的條件不穩定,請考慮先使用 vi.waitFor 來等待條件滿足:
ts
import { expect, vi } from 'vitest';

const flakyValue = await vi.waitFor(() => getFlakyValue());
expect(flakyValue).toMatchSnapshot();
  • 不支援 .resolves 和 .rejects。如果條件是非同步的,expect.poll 已經會自動等待它。
  • 不支援 toThrow 及其別名,因為 expect.poll 條件總是在匹配器獲取值之前就已經被 resolve 了。

not ​

使用 not 來否定斷言。例如,以下程式碼斷言 input 值不等於 2。如果相等,則斷言將拋出錯誤,測試將失敗。

ts
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 個蘋果。

ts
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 參數限制要檢查的小數點後位數。例如:

ts
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。一個常見的用途是檢查函式是否有回傳任何值。

ts
import { expect, test } from 'vitest';

function getApples() {
  return 3;
}

test('function returned something', () => {
  expect(getApples()).toBeDefined();
});

toBeUndefined ​

  • 類型: () => Awaitable<void>

與 toBeDefined 相反,toBeUndefined 斷言該值是否等於 undefined。一個常見的用途是檢查函式是否尚未回傳任何值。

ts
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 的回傳值 - 它可能是一個複雜的物件、一個字串或任何其他內容。該程式碼仍然可以工作。

ts
import { Stocks } from './stocks.js';

const stocks = new Stocks();
stocks.sync('Bill');
if (stocks.getInfo('Bill')) stocks.sell('apples', 'Bill');

因此,如果您想測試 stocks.getInfo 是否為真,您可以編寫:

ts
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 的回傳值 - 它可能會回傳任何假值,但該程式碼仍然可以工作。

ts
import { Stocks } from './stocks.js';

const stocks = new Stocks();
stocks.sync('Bill');
if (!stocks.stockFailed('Bill')) stocks.sell('apples', 'Bill');

因此,如果您想測試 stocks.stockFailed 是否為假,您可以編寫:

ts
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) 的別名。

ts
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) 的別名。

ts
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 用於斷言實際值的類型是否符合預期類型。

ts
import { expect, test } from 'vitest';

const actual = 'stock';

test('stock is type of string', () => {
  expect(actual).toBeTypeOf('string');
});

toBeInstanceOf ​

  • 類型: (c: any) => Awaitable<void>

toBeInstanceOf 用於斷言實際值是否為指定類別的實例。

ts
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 用於斷言實際值是否大於預期值。相等的值將導致測試失敗。

ts
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 用於斷言實際值是否大於或等於預期值。

ts
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 用於斷言實際值是否小於預期值。相等的值將導致測試失敗。

ts
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 用於斷言實際值是否小於或等於預期值。

ts
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 之間的差異:

ts
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 的物件字面值。
ts
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 中,或者一個元素是否在另一個元素內部。

ts
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。

ts
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 屬性,且該屬性的值為特定的數值。

ts
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 匹配器,來比較接收到的屬性值。

ts
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 斷言字串是否符合正則表達式或另一個字串。

ts
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,此方法不允許接收到的陣列包含額外的元素。

ts
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') 是否拋出錯誤,我們可以這樣寫:

ts
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 結合使用。

js
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 始終更新測試。

ts
import { expect, test } from 'vitest';

test('matches snapshot', () => {
  const data = { foo: new Set(['bar', 'snapshot']) };
  expect(data).toMatchSnapshot();
});

如果您只需要測試物件的結構,而不需要完全匹配,您可以提供物件結構的描述:

ts
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 檔案)。

ts
import { expect, test } from 'vitest';

test('matches inline snapshot', () => {
  const data = { foo: new Set(['bar', 'snapshot']) };
  // Vitest 將在更新快照時更新以下內容
  expect(data).toMatchInlineSnapshot(`
    {
      "foo": Set {
        "bar",
        "snapshot",
      },
    }
  `);
});

如果您只需要測試物件的結構,而不需要完全匹配,您可以提供物件結構的描述:

ts
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, message?: string) => Promise<void>

將快照與明確指定路徑的檔案內容進行比較或更新 (此方式不使用 .snap 檔案)。

ts
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 ​

  • 類型: (message?: string) => void

與 toMatchSnapshot 相同,但期望與 toThrowError 相同的值。

toThrowErrorMatchingInlineSnapshot ​

  • 類型: (snapshot?: string, message?: string) => void

與 toMatchInlineSnapshot 相同,但期望與 toThrowError 相同的值。

toHaveBeenCalled ​

  • 類型: () => Awaitable<void>

此斷言對於測試函數是否已被呼叫非常有用。 需傳入 spy 函數至 expect。

ts
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。

ts
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。

ts
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。

ts
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。

ts
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。

ts
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。

ts
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。

ts
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>

您可以呼叫此斷言以檢查函式在最後一次呼叫時是否成功傳回特定值。需要將一個 spy 函式傳遞給 expect。

ts
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。

ts
import { expect, test, vi } from 'vitest';

test('間諜函數在第二次呼叫時回傳香蕉', () => {
  const sell = vi.fn((product: string) => ({ product }));

  sell('apples');
  sell('bananas');

  expect(sell).toHaveNthReturnedWith(2, { product: 'bananas' });
});

toHaveResolved ​

  • 類型别名: () => Awaitable<void>

這個斷言檢查一個函數是否成功解析(即沒有拒絕)值至少一次。需要傳遞一個 spy 函數給 expect。

如果函數回傳了一個 promise,但是尚未解析,這將會失敗。

ts
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,但是尚未解析,它將不會被計算。

ts
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,但是尚未解析,這將會失敗。

ts
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,但是尚未解析,這將會失敗。

ts
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,但是尚未解析,這將會失敗。

ts
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>

此斷言檢查值是否符合特定條件。

ts
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 呼叫並傳回一些資料,您可以使用以下程式碼來斷言其回傳值:

ts
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)。

從 Vitest 2.2 開始,如果方法未被 await,Vitest 將在測試結束時顯示警告。在 Vitest 3 中,如果斷言未被 await,測試將被標記為“失敗”。

rejects ​

  • 類型: Promisify<Assertions>

rejects 旨在簡化斷言非同步程式碼的流程,減少重複程式碼。 使用它來取得 Promise 被拒絕的原因,並使用常見的斷言來驗證其值。 如果 Promise 成功解析,則斷言將會失敗。

它傳回相同的 Assertions 物件,但所有匹配器現在都傳回 Promise,因此您必須使用 await 等待它完成。 也適用於 chai 斷言。

例如,如果您有一個函數在您呼叫它時會失敗,您可以使用以下程式碼來斷言原因:

ts
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)。

從 Vitest 2.2 開始,如果方法未被 await,Vitest 將在測試結束時顯示警告。在 Vitest 3 中,如果斷言未被 await,測試將被標記為“失敗”。

expect.assertions ​

  • 類型: (count: number) => void

確認在測試期間是否呼叫了特定數量的斷言,無論測試通過或失敗。 一個常見的使用情境是檢查是否呼叫了非同步程式碼。

例如,如果我們有一個非同步函數會呼叫兩個匹配器,我們可以斷言它們是否被實際呼叫。

ts
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

確認在測試期間是否至少呼叫了一個斷言,無論測試通過或失敗。 一個常見的使用情境是檢查是否呼叫了非同步程式碼。

例如,如果您有一段程式碼呼叫一個回呼函數,我們可以在回呼函數中進行斷言,但如果我們不檢查是否呼叫了斷言,則測試將始終通過。

ts
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 資料夾而拋出錯誤,並且還分別處理每個錯誤,我們可以這樣做:

ts
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。 如果您只想確認屬性存在,這會很有用。

ts
import { expect, test } from 'vitest';

test('物件具有 "apples" 鍵', () => {
  expect({ apples: 22 }).toEqual({ apples: expect.anything() });
});

expect.any ​

  • 類型: (constructor: unknown) => any

當與相等性檢查一起使用時,此非對稱匹配器僅在值是指定建構子的實例時才傳回 true。 如果您有一個每次都會產生數值的值,並且您只想確認它是否存在且具有正確的類型,這會很有用。

ts
import { expect, test } from 'vitest';
import { generateId } from './generators.js';

test('"id" 是一個數字', () => {
  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 位精度通過:

js
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。

ts
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。

ts
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。

ts
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。

ts
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 一樣使用的非對稱匹配器。

ts
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 介面:

ts
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 相容。

ts
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('自訂相等性測試器', () => {
  expect(new AnagramComparator('listen')).toEqual(
    new AnagramComparator('silent')
  );
});
Pager
上一頁Vi
下一頁expectTypeOf

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors

https://v2.vitest.dev/api/expect

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors