Skip to content
Vitest 3
Main Navigation 指南 & API配置瀏覽器模式進階 API
3.2.0
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

簡介

為何選擇 Vitest

快速入門

功能特色

配置參考

API

測試 API 參考

模擬函式

Vi

expect

expectTypeOf

assert

assertType

指南

命令列介面

測試篩選

測試專案

報告器

程式碼覆蓋率

快照

模擬(Mocking)

平行化

型別測試

Vitest UI

內聯測試

測試上下文

測試註解

測試環境

擴展匹配器

IDE 整合

偵錯

常見錯誤

遷移指南

遷移到 Vitest 3.0

從 Jest 遷移

效能

測試效能分析

提升效能

瀏覽器模式

進階 API

與其他測試執行器的比較

本頁導覽

Vi ​

Vitest 透過其 vi 輔助工具提供實用功能。您可以在全域範圍內存取它(當 globals 配置啟用時),或直接從 vitest 匯入:

js
import { vi } from 'vitest';

模擬模組 ​

本節說明您在模擬模組時可以使用的 API。請注意,Vitest 不支援模擬使用 require() 匯入的模組。

vi.mock ​

  • 類型:(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
  • 類型:<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void

將所有從提供的 path 匯入的模組替換為另一個模組。您可以在路徑中使用已配置的 Vite 別名。對 vi.mock 的呼叫會被提升(hoisted),因此您在哪裡呼叫它並不重要。它將始終在所有匯入之前執行。如果您需要引用其範圍之外的一些變數,您可以將它們定義在 vi.hoisted 內部,並在 vi.mock 內部引用它們。

WARNING

vi.mock 僅適用於使用 import 關鍵字匯入的模組。它不適用於 require。

為了提升 vi.mock,Vitest 會靜態分析您的檔案。這表示未直接從 vitest 套件匯入的 vi(例如,從某些工具檔案匯入)不能使用。請使用從 vitest 匯入的 vi 來呼叫 vi.mock,或啟用 globals 配置選項。

Vitest 不會模擬在 設定檔案中匯入的模組,因為在測試檔案執行時它們已被快取。您可以在 vi.hoisted 內部呼叫 vi.resetModules() 以在執行測試檔案之前清除所有模組快取。

如果定義了 factory 函數,所有匯入都將返回其結果。Vitest 只呼叫 factory 一次,並為所有後續匯入快取結果,直到呼叫 vi.unmock 或 vi.doUnmock。

與 jest 不同,factory 可以是異步的。您可以使用 vi.importActual 或將 factory 作為第一個參數傳入的輔助函數,並在內部取得原始模組。

您也可以提供一個帶有 spy 屬性的物件,而不是 factory 函數。如果 spy 為 true,則 Vitest 將像往常一樣自動模擬模組,但它不會覆寫匯出的實作。如果您只是想斷言匯出的方法是否被另一個方法正確呼叫,這會很有用。

ts
import { calculator } from './src/calculator.ts';

vi.mock('./src/calculator.ts', { spy: true });

// 呼叫原始實作,
// 但允許稍後斷言行為
const result = calculator(1, 2);

expect(result).toBe(3);
expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);

Vitest 還支援在 vi.mock 和 vi.doMock 方法中使用模組 Promise 而不是字串,以提供更好的 IDE 支援。當檔案移動時,路徑將會更新,並且 importOriginal 會自動繼承類型。使用此簽章還將強制 factory 返回類型與原始模組相容(保持匯出可選)。

ts
// @filename: ./path/to/module.js
export declare function total(...numbers: number[]): number;
// @filename: test.js
import { vi } from 'vitest';
// ---cut---
vi.mock(import('./path/to/module.js'), async importOriginal => {
  const mod = await importOriginal(); // 類型會被推斷
  //    ^?
  return {
    ...mod,
    // 替換一些匯出
    total: vi.fn(),
  };
});

在底層,Vitest 仍然在字串而不是模組物件上操作。

但是,如果您在 tsconfig.json 中配置了 paths 別名並使用 TypeScript,編譯器將無法正確解析匯入類型。 為了使其正常運作,請確保將所有別名匯入替換為其對應的相對路徑。 例如,使用 import('./path/to/module.js') 而不是 import('@/module')。

WARNING

vi.mock 會被提升(換句話說,移動)到檔案頂部。這意味著無論您在哪裡編寫它(無論是在 beforeEach 還是 test 內部),它實際上都會在之前被呼叫。

這也意味著您不能在 factory 內部使用在 factory 外部定義的任何變數。

如果您需要在 factory 內部使用變數,請嘗試 vi.doMock。它的工作方式相同,但不會被提升。請注意,它只會模擬後續的匯入。

如果 vi.hoisted 方法在 vi.mock 之前聲明,您也可以引用它定義的變數:

ts
import { namedExport } from './path/to/module.js';

const mocks = vi.hoisted(() => {
  return {
    namedExport: vi.fn(),
  };
});

vi.mock('./path/to/module.js', () => {
  return {
    namedExport: mocks.namedExport,
  };
});

vi.mocked(namedExport).mockReturnValue(100);

expect(namedExport()).toBe(100);
expect(namedExport).toBe(mocks.namedExport);

WARNING

如果您正在模擬一個帶有預設匯出的模組,您將需要在返回的 factory 函數物件中提供一個 default 鍵。這是 ES 模組特有的注意事項;因此,jest 文件可能有所不同,因為 jest 使用 CommonJS 模組。例如:

ts
vi.mock('./path/to/module.js', () => {
  return {
    default: { myDefaultKey: vi.fn() },
    namedExport: vi.fn(),
    // 等等...
  };
});

如果被模擬的檔案旁邊有一個 __mocks__ 資料夾,並且沒有提供 factory,Vitest 將嘗試在 __mocks__ 子資料夾中找到一個同名檔案並將其用作實際模組。如果您正在模擬一個依賴項,Vitest 將嘗試在專案的 根目錄(預設為 process.cwd())中找到一個 __mocks__ 資料夾。您可以透過 deps.moduleDirectories 配置選項告訴 Vitest 依賴項的位置。

例如,您有以下檔案結構:

- __mocks__
  - axios.js
- src
  __mocks__
    - increment.js
  - increment.js
- tests
  - increment.test.js

如果您在測試檔案中呼叫 vi.mock 而沒有提供 factory 或選項,它將在 __mocks__ 資料夾中找到一個檔案作為模組使用:

ts
import { vi } from 'vitest';

// axios 是來自 `__mocks__/axios.js` 的預設匯出
import axios from 'axios';

// increment 是來自 `src/__mocks__/increment.js` 的具名匯出
import { increment } from '../increment.js';

vi.mock('axios');
vi.mock('../increment.js');

axios.get(`/apples/${increment(1)}`);

WARNING

請注意,如果您不呼叫 vi.mock,模組不會自動模擬。要複製 Jest 的自動模擬行為,您可以在 setupFiles 內部為每個所需模組呼叫 vi.mock。

如果沒有 __mocks__ 資料夾或未提供 factory,Vitest 將匯入原始模組並自動模擬其所有匯出。有關應用的規則,請參閱演算法。

vi.doMock ​

  • 類型:(path: string, factory?: MockOptions | ((importOriginal: () => unknown) => unknown)) => void
  • 類型:<T>(path: Promise<T>, factory?: MockOptions | ((importOriginal: () => T) => T | Promise<T>)) => void

與 vi.mock 相同,但它不會被提升到檔案頂部,因此您可以在全域檔案範圍內引用變數。模組的下一個動態匯入將被模擬。

WARNING

這不會模擬在此呼叫之前匯入的模組。別忘了 ESM 中的所有靜態匯入總是會被提升,因此將其放在靜態匯入之前不會強制它在匯入之前被呼叫:

ts
vi.doMock('./increment.js'); // 這將在 import 語句 _之後_ 被呼叫

import { increment } from './increment.js';
ts
export function increment(number) {
  return number + 1;
}
ts
import { beforeEach, test } from 'vitest';
import { increment } from './increment.js';

// 模組未被模擬,因為 vi.doMock 尚未被呼叫
increment(1) === 2;

let mockedIncrement = 100;

beforeEach(() => {
  // 您可以在 factory 內部存取變數
  vi.doMock('./increment.js', () => ({ increment: () => ++mockedIncrement }));
});

test('匯入下一個模組會匯入模擬的模組', async () => {
  // 原始匯入未被模擬,因為 vi.doMock 在匯入之後才被評估
  expect(increment(1)).toBe(2);
  const { increment: mockedIncrement } = await import('./increment.js');
  // 新的動態匯入返回模擬的模組
  expect(mockedIncrement(1)).toBe(101);
  expect(mockedIncrement(1)).toBe(102);
  expect(mockedIncrement(1)).toBe(103);
});

vi.mocked ​

  • 類型:<T>(obj: T, deep?: boolean) => MaybeMockedDeep<T>
  • 類型:<T>(obj: T, options?: { partial?: boolean; deep?: boolean }) => MaybePartiallyMockedDeep<T>

TypeScript 的類型輔助工具。只返回傳入的物件。

當 partial 為 true 時,它將期望 Partial<T> 作為返回值。預設情況下,這只會讓 TypeScript 認為第一層的值被模擬了。您可以將 { deep: true } 作為第二個參數傳遞,以告訴 TypeScript 整個物件都被模擬了,如果它確實如此。

ts
export function add(x: number, y: number): number {
  return x + y;
}

export function fetchSomething(): Promise<Response> {
  return fetch('https://vitest.dev/');
}
ts
import * as example from './example';

vi.mock('./example');

test('1 + 1 等於 10', async () => {
  vi.mocked(example.add).mockReturnValue(10);
  expect(example.add(1, 1)).toBe(10);
});

test('只用部分正確的類型模擬返回值', async () => {
  vi.mocked(example.fetchSomething).mockResolvedValue(new Response('hello'));
  vi.mocked(example.fetchSomething, { partial: true }).mockResolvedValue({
    ok: false,
  });
  // vi.mocked(example.someFn).mockResolvedValue({ ok: false }) // 這是一個類型錯誤
});

vi.importActual ​

  • 類型:<T>(path: string) => Promise<T>

匯入模組,繞過所有是否應模擬的檢查。如果您想部分模擬模組,這會很有用。

ts
vi.mock('./example.js', async () => {
  const originalModule = await vi.importActual('./example.js');

  return { ...originalModule, get: vi.fn() };
});

vi.importMock ​

  • 類型:<T>(path: string) => Promise<MaybeMockedDeep<T>>

匯入一個模組,其所有屬性(包括巢狀屬性)都被模擬。遵循與 vi.mock 相同的規則。有關應用的規則,請參閱演算法。

vi.unmock ​

  • 類型:(path: string | Promise<Module>) => void

從模擬註冊表中移除模組。所有匯入呼叫都將返回原始模組,即使它之前已被模擬。此呼叫會被提升到檔案頂部,因此它只會取消模擬在設定檔案中定義的模組,例如。

vi.doUnmock ​

  • 類型:(path: string | Promise<Module>) => void

與 vi.unmock 相同,但不會被提升到檔案頂部。模組的下一個匯入將匯入原始模組而不是模擬。這不會取消模擬先前匯入的模組。

ts
export function increment(number) {
  return number + 1;
}
ts
import { increment } from './increment.js';

// increment 已經被模擬,因為 vi.mock 被提升了
increment(1) === 100;

// 這被提升了,並且 factory 在第 1 行的 import 之前被呼叫
vi.mock('./increment.js', () => ({ increment: () => 100 }));

// 所有呼叫都被模擬,並且 `increment` 總是返回 100
increment(1) === 100;
increment(30) === 100;

// 這沒有被提升,所以其他 import 將返回未模擬的模組
vi.doUnmock('./increment.js');

// 這仍然返回 100,因為 `vi.doUnmock` 不會重新評估模組
increment(1) === 100;
increment(30) === 100;

// 下一個 import 未被模擬,現在 `increment` 是返回 count + 1 的原始函數
const { increment: unmockedIncrement } = await import('./increment.js');

unmockedIncrement(1) === 2;
unmockedIncrement(30) === 31;

vi.resetModules ​

  • 類型:() => Vitest

透過清除所有模組的快取來重設模組註冊表。這允許模組在重新匯入時重新評估。頂層匯入無法重新評估。這對於隔離測試之間本機狀態衝突的模組可能很有用。

ts
import { vi } from 'vitest';

import { data } from './data.js'; // 每個測試都不會重新評估

beforeEach(() => {
  vi.resetModules();
});

test('改變狀態', async () => {
  const mod = await import('./some/path.js'); // 將會重新評估
  mod.changeLocalState('新值');
  expect(mod.getLocalState()).toBe('新值');
});

test('模組具有舊狀態', async () => {
  const mod = await import('./some/path.js'); // 將會重新評估
  expect(mod.getLocalState()).toBe('舊值');
});

WARNING

不會重設模擬註冊表。要清除模擬註冊表,請使用 vi.unmock 或 vi.doUnmock。

vi.dynamicImportSettled ​

等待所有匯入載入完成。如果您有一個同步呼叫開始匯入一個您無法以其他方式等待的模組,這會很有用。

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

// 無法追蹤匯入,因為沒有返回 Promise
function renderComponent() {
  import('./component.js').then(({ render }) => {
    render();
  });
}

test('操作已解決', async () => {
  renderComponent();
  await vi.dynamicImportSettled();
  expect(document.querySelector('.component')).not.toBeNull();
});

TIP

如果在動態匯入期間啟動了另一個動態匯入,此方法將等待所有這些匯入都解析。

此方法還將在匯入解析後等待下一個 setTimeout 刻度,因此所有同步操作應在解析時完成。

模擬函數和物件 ​

本節說明如何使用方法模擬並替換環境和全域變數。

vi.fn ​

  • 類型: (fn?: Function) => Mock

在函數上建立一個監聽器,儘管可以不帶函數啟動。每次呼叫函數時,它都會儲存其呼叫參數、返回值和實例。此外,您可以使用方法來操作其行為。 如果沒有給定函數,則在呼叫時,模擬將返回 undefined。

ts
const getApples = vi.fn(() => 0);

getApples();

expect(getApples).toHaveBeenCalled();
expect(getApples).toHaveReturnedWith(0);

getApples.mockReturnValueOnce(5);

const res = getApples();
expect(res).toBe(5);
expect(getApples).toHaveNthReturnedWith(2, 5);

vi.mockObject 3.2.0+ ​

  • 類型: <T>(value: T) => MaybeMockedDeep<T>

以與 vi.mock() 模擬模組匯出相同的方式,深度模擬給定物件的屬性和方法。詳情請參閱自動模擬。

ts
const original = {
  simple: () => 'value',
  nested: {
    method: () => 'real',
  },
  prop: 'foo',
};

const mocked = vi.mockObject(original);
expect(mocked.simple()).toBe(undefined);
expect(mocked.nested.method()).toBe(undefined);
expect(mocked.prop).toBe('foo');

mocked.simple.mockReturnValue('mocked');
mocked.nested.method.mockReturnValue('mocked nested');

expect(mocked.simple()).toBe('mocked');
expect(mocked.nested.method()).toBe('mocked nested');

vi.isMockFunction ​

  • 類型: (fn: Function) => boolean

檢查給定參數是否為模擬函數。如果您使用 TypeScript,它還會縮小其類型。

vi.clearAllMocks ​

在所有監聽器上呼叫 .mockClear()。 這將清除模擬歷史,而不影響模擬實作。

vi.resetAllMocks ​

在所有監聽器上呼叫 .mockReset()。 這將清除模擬歷史並將每個模擬的實作重設為其原始狀態。

vi.restoreAllMocks ​

在所有監聽器上呼叫 .mockRestore()。 這將清除模擬歷史,還原所有原始模擬實作,並還原被監聽物件的原始描述符。

vi.spyOn ​

  • 類型: <T, K extends keyof T>(object: T, method: K, accessType?: 'get' | 'set') => MockInstance

在物件的方法或 getter/setter 上建立一個監聽器,類似於 vi.fn()。它返回一個模擬函數。

ts
let apples = 0;
const cart = {
  getApples: () => 42,
};

const spy = vi.spyOn(cart, 'getApples').mockImplementation(() => apples);
apples = 1;

expect(cart.getApples()).toBe(1);

expect(spy).toHaveBeenCalled();
expect(spy).toHaveReturnedWith(1);

TIP

在支援 Explicit Resource Management 的環境中,您可以使用 using 而不是 const,以便在包含區塊退出時自動呼叫任何模擬函數上的 mockRestore。這對於被監聽的方法特別有用:

ts
it('呼叫 console.log', () => {
  using spy = vi.spyOn(console, 'log').mockImplementation(() => {})
  debug('message')
  expect(spy).toHaveBeenCalled()
})
// console.log 在此處被還原

TIP

您可以在 afterEach 內部呼叫 vi.restoreAllMocks(或啟用 test.restoreMocks)以將所有方法還原為其原始實作。這將還原原始的物件描述符,因此您將無法更改方法的實作:

ts
const cart = {
  getApples: () => 42,
};

const spy = vi.spyOn(cart, 'getApples').mockReturnValue(10);

console.log(cart.getApples()); // 10
vi.restoreAllMocks();
console.log(cart.getApples()); // 42
spy.mockReturnValue(10);
console.log(cart.getApples()); // 仍然是 42!

TIP

在 瀏覽器模式 中無法監聽匯出的方法。相反,您可以透過呼叫 vi.mock("./file-path.js", { spy: true }) 來監聽每個匯出的方法。這將模擬每個匯出,但保持其實作不變,讓您可以斷言方法是否被正確呼叫。

ts
import { calculator } from './src/calculator.ts';

vi.mock('./src/calculator.ts', { spy: true });

calculator(1, 2);

expect(calculator).toHaveBeenCalledWith(1, 2);
expect(calculator).toHaveReturned(3);

雖然在 jsdom 或其他 Node.js 環境中可以監聽匯出,但這在未來可能會改變。

vi.stubEnv ​

  • 類型: <T extends string>(name: T, value: T extends "PROD" | "DEV" | "SSR" ? boolean : string | undefined) => Vitest

更改 process.env 和 import.meta.env 上的環境變數值。您可以透過呼叫 vi.unstubAllEnvs 來還原其值。

ts
import { vi } from 'vitest';

// 在呼叫 "vi.stubEnv" 之前,`process.env.NODE_ENV` 和 `import.meta.env.NODE_ENV`
// 都是 "development"

vi.stubEnv('NODE_ENV', 'production');

process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';

vi.stubEnv('NODE_ENV', undefined);

process.env.NODE_ENV === undefined;
import.meta.env.NODE_ENV === undefined;

// 不會改變其他環境變數
import.meta.env.MODE === 'development';

TIP

您也可以透過簡單地賦值來更改值,但您將無法使用 vi.unstubAllEnvs 來還原先前的值:

ts
import.meta.env.MODE = 'test';

vi.unstubAllEnvs ​

  • 類型: () => Vitest

還原所有透過 vi.stubEnv 更改的 import.meta.env 和 process.env 值。當它第一次被呼叫時,Vitest 會記住原始值並將其儲存起來,直到再次呼叫 unstubAllEnvs。

ts
import { vi } from 'vitest';

// 在呼叫 stubEnv 之前,`process.env.NODE_ENV` 和 `import.meta.env.NODE_ENV`
// 都是 "development"

vi.stubEnv('NODE_ENV', 'production');

process.env.NODE_ENV === 'production';
import.meta.env.NODE_ENV === 'production';

vi.stubEnv('NODE_ENV', 'staging');

process.env.NODE_ENV === 'staging';
import.meta.env.NODE_ENV === 'staging';

vi.unstubAllEnvs();

// 還原到第一次 "stubEnv" 呼叫之前儲存的值
process.env.NODE_ENV === 'development';
import.meta.env.NODE_ENV === 'development';

vi.stubGlobal ​

  • 類型: (name: string | number | symbol, value: unknown) => Vitest

更改全域變數的值。您可以透過呼叫 vi.unstubAllGlobals 來還原其原始值。

ts
import { vi } from 'vitest';

// 在呼叫 stubGlobal 之前,`innerWidth` 是 "0"

vi.stubGlobal('innerWidth', 100);

innerWidth === 100;
globalThis.innerWidth === 100;
// 如果您使用 jsdom 或 happy-dom
window.innerWidth === 100;

TIP

您也可以透過簡單地賦值給 globalThis 或 window(如果您使用 jsdom 或 happy-dom 環境)來更改值,但您將無法使用 vi.unstubAllGlobals 來還原原始值:

ts
globalThis.innerWidth = 100;
// 如果您使用 jsdom 或 happy-dom
window.innerWidth = 100;

vi.unstubAllGlobals ​

  • 類型: () => Vitest

還原所有透過 vi.stubGlobal 更改的 globalThis/global(以及 window/top/self/parent,如果您使用 jsdom 或 happy-dom 環境)上的全域值。當它第一次被呼叫時,Vitest 會記住原始值並將其儲存起來,直到再次呼叫 unstubAllGlobals。

ts
import { vi } from 'vitest';

const Mock = vi.fn();

// 在呼叫 "stubGlobal" 之前,IntersectionObserver 是 "undefined"

vi.stubGlobal('IntersectionObserver', Mock);

IntersectionObserver === Mock;
global.IntersectionObserver === Mock;
globalThis.IntersectionObserver === Mock;
// 如果您使用 jsdom 或 happy-dom
window.IntersectionObserver === Mock;

vi.unstubAllGlobals();

globalThis.IntersectionObserver === undefined;
'IntersectionObserver' in globalThis === false;
// 拋出 ReferenceError,因為它未定義
IntersectionObserver === undefined;

假計時器 ​

本節說明如何使用假計時器。

vi.advanceTimersByTime ​

  • 類型: (ms: number) => Vitest

此方法將呼叫每個已啟動的計時器,直到經過指定毫秒數或佇列為空為止——以先發生者為準。

ts
let i = 0;
setInterval(() => console.log(++i), 50);

vi.advanceTimersByTime(150);

// log: 1
// log: 2
// log: 3

vi.advanceTimersByTimeAsync ​

  • 類型: (ms: number) => Promise<Vitest>

此方法將呼叫每個已啟動的計時器,直到經過指定毫秒數或佇列為空為止——以先發生者為準。這將包括異步設定的計時器。

ts
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);

await vi.advanceTimersByTimeAsync(150);

// log: 1
// log: 2
// log: 3

vi.advanceTimersToNextTimer ​

  • 類型: () => Vitest

將呼叫下一個可用的計時器。這對於在每次計時器呼叫之間進行斷言很有用。您可以鏈式呼叫它來自行管理計時器。

ts
let i = 0;
setInterval(() => console.log(++i), 50);

vi.advanceTimersToNextTimer() // log: 1
  .advanceTimersToNextTimer() // log: 2
  .advanceTimersToNextTimer(); // log: 3

vi.advanceTimersToNextTimerAsync ​

  • 類型: () => Promise<Vitest>

將呼叫下一個可用的計時器,如果它是異步設定的,則會等待它解析。這對於在每次計時器呼叫之間進行斷言很有用。

ts
let i = 0;
setInterval(() => Promise.resolve().then(() => console.log(++i)), 50);

await vi.advanceTimersToNextTimerAsync(); // log: 1
expect(console.log).toHaveBeenCalledWith(1);

await vi.advanceTimersToNextTimerAsync(); // log: 2
await vi.advanceTimersToNextTimerAsync(); // log: 3

vi.advanceTimersToNextFrame 2.1.0+ ​

  • 類型: () => Vitest

類似於 vi.advanceTimersByTime,但會根據目前使用 requestAnimationFrame 排程的回呼所需的毫秒數來推進計時器。

ts
let frameRendered = false;

requestAnimationFrame(() => {
  frameRendered = true;
});

vi.advanceTimersToNextFrame();

expect(frameRendered).toBe(true);

vi.getTimerCount ​

  • 類型: () => number

取得等待中的計時器數量。

vi.clearAllTimers ​

移除所有排程執行的計時器。這些計時器將來永遠不會執行。

vi.getMockedSystemTime ​

  • 類型:() => Date | null

返回模擬的當前日期。如果日期未被模擬,該方法將返回 null。

vi.getRealSystemTime ​

  • 類型:() => number

當使用 vi.useFakeTimers 時,Date.now 呼叫會被模擬。如果您需要取得真實的毫秒時間,可以呼叫此函數。

vi.runAllTicks ​

  • 類型: () => Vitest

呼叫所有由 process.nextTick 排入佇列的微任務。這也將執行所有由自身排程的微任務。

vi.runAllTimers ​

  • 類型: () => Vitest

此方法將呼叫每個已啟動的計時器,直到計時器佇列為空。這表示在 runAllTimers 期間呼叫的每個計時器都將被觸發。如果您有一個無限間隔,它將在 10,000 次嘗試後拋出錯誤(可透過 fakeTimers.loopLimit 配置)。

ts
let i = 0;
setTimeout(() => console.log(++i));
const interval = setInterval(() => {
  console.log(++i);
  if (i === 3) {
    clearInterval(interval);
  }
}, 50);

vi.runAllTimers();

// log: 1
// log: 2
// log: 3

vi.runAllTimersAsync ​

  • 類型: () => Promise<Vitest>

此方法將異步呼叫每個已啟動的計時器,直到計時器佇列為空。這表示在 runAllTimersAsync 期間呼叫的每個計時器都將被觸發,即使是異步計時器。如果您有一個無限間隔, 它將在 10,000 次嘗試後拋出錯誤(可透過 fakeTimers.loopLimit 配置)。

ts
setTimeout(async () => {
  console.log(await Promise.resolve('result'));
}, 100);

await vi.runAllTimersAsync();

// log: result

vi.runOnlyPendingTimers ​

  • 類型: () => Vitest

此方法將呼叫所有在 vi.useFakeTimers 呼叫之後啟動的計時器。它不會觸發在呼叫期間啟動的任何計時器。

ts
let i = 0;
setInterval(() => console.log(++i), 50);

vi.runOnlyPendingTimers();

// log: 1

vi.runOnlyPendingTimersAsync ​

  • 類型: () => Promise<Vitest>

此方法將異步呼叫所有在 vi.useFakeTimers 呼叫之後啟動的計時器,甚至是異步計時器。它不會觸發在呼叫期間啟動的任何計時器。

ts
setTimeout(() => {
  console.log(1);
}, 100);
setTimeout(() => {
  Promise.resolve().then(() => {
    console.log(2);
    setInterval(() => {
      console.log(3);
    }, 40);
  });
}, 10);

await vi.runOnlyPendingTimersAsync();

// log: 2
// log: 3
// log: 3
// log: 1

vi.setSystemTime ​

  • 類型:(date: string | number | Date) => void

如果啟用假計時器,此方法會模擬使用者更改系統時鐘(將影響與日期相關的 API,例如 hrtime、performance.now 或 new Date())——但是,它不會觸發任何計時器。如果未啟用假計時器,此方法只會模擬 Date.* 呼叫。

如果您需要測試任何依賴於當前日期的內容,例如程式碼中的 Luxon 呼叫,這會很有用。

接受與 Date 相同的字串和數字參數。

ts
const date = new Date(1998, 11, 19);

vi.useFakeTimers();
vi.setSystemTime(date);

expect(Date.now()).toBe(date.valueOf());

vi.useRealTimers();

vi.useFakeTimers ​

  • 類型: (config?: FakeTimerInstallOpts) => Vitest

要啟用模擬計時器,您需要呼叫此方法。它將包裝所有後續對計時器的呼叫(例如 setTimeout、setInterval、clearTimeout、clearInterval、setImmediate、clearImmediate 和 Date),直到呼叫 vi.useRealTimers()。

當 Vitest 在 node:child_process 內部使用 --pool=forks 執行時,不支援模擬 nextTick。NodeJS 在 node:child_process 內部使用 process.nextTick,當它被模擬時會掛起。當 Vitest 使用 --pool=threads 執行時,支援模擬 nextTick。

實作內部基於 @sinonjs/fake-timers。

TIP

vi.useFakeTimers() 不會自動模擬 process.nextTick 和 queueMicrotask。 但您可以透過在 toFake 參數中指定選項來啟用它:vi.useFakeTimers({ toFake: ['nextTick', 'queueMicrotask'] })。

vi.isFakeTimers ​

  • 類型: () => boolean

如果啟用假計時器,則返回 true。

vi.useRealTimers ​

  • 類型: () => Vitest

當計時器用盡時,您可以呼叫此方法將模擬計時器還原為其原始實作。所有先前排程的計時器都將被丟棄。

其他 ​

Vitest 提供的一組實用輔助函數。

vi.waitFor ​

  • 類型: <T>(callback: WaitForCallback<T>, options?: number | WaitForOptions) => Promise<T>

等待回呼成功執行。如果回呼拋出錯誤或返回被拒絕的 Promise,它將繼續等待直到成功或超時。

如果選項設定為數字,則效果等同於設定 { timeout: options }。

當您需要等待某些異步操作完成時,這會非常有用,例如,當您啟動伺服器並需要等待它啟動時。

ts
import { expect, test, vi } from 'vitest';
import { createServer } from './server.js';

test('伺服器成功啟動', async () => {
  const server = createServer();

  await vi.waitFor(
    () => {
      if (!server.isReady) {
        throw new Error('伺服器未啟動');
      }

      console.log('伺服器已啟動');
    },
    {
      timeout: 500, // 預設為 1000
      interval: 20, // 預設為 50
    }
  );
  expect(server.isReady).toBe(true);
});

它也適用於異步回呼

ts
// @vitest-environment jsdom

import { expect, test, vi } from 'vitest';
import { getDOMElementAsync, populateDOMAsync } from './dom.js';

test('元素存在於 DOM 中', async () => {
  // 開始填充 DOM
  populateDOMAsync();

  const element = await vi.waitFor(
    async () => {
      // 嘗試取得元素直到它存在
      const element = (await getDOMElementAsync()) as HTMLElement | null;
      expect(element).toBeTruthy();
      expect(element.dataset.initialized).toBeTruthy();
      return element;
    },
    {
      timeout: 500, // 預設為 1000
      interval: 20, // 預設為 50
    }
  );
  expect(element).toBeInstanceOf(HTMLElement);
});

如果使用 vi.useFakeTimers,vi.waitFor 會在每次檢查回呼中自動呼叫 vi.advanceTimersByTime(interval)。

vi.waitUntil ​

  • 類型: <T>(callback: WaitUntilCallback<T>, options?: number | WaitUntilOptions) => Promise<T>

這類似於 vi.waitFor,但如果回呼拋出任何錯誤,執行會立即中斷並收到錯誤訊息。如果回呼返回假值,下一次檢查將繼續,直到返回真值。當您需要等待某物存在才能執行下一步時,這會很有用。

請看下面的範例。我們可以使用 vi.waitUntil 等待元素出現在頁面上,然後我們可以對該元素執行某些操作。

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

test('元素正確渲染', async () => {
  const element = await vi.waitUntil(() => document.querySelector('.element'), {
    timeout: 500, // 預設為 1000
    interval: 20, // 預設為 50
  });

  // 對元素執行某些操作
  expect(element.querySelector('.element-child')).toBeTruthy();
});

vi.hoisted ​

  • 類型:<T>(factory: () => T) => T

ES 模組中的所有靜態 import 語句都會被提升到檔案頂部,因此在匯入之前定義的任何程式碼實際上都會在匯入評估之後執行。

然而,在匯入模組之前呼叫一些副作用(例如模擬日期)可能很有用。

要繞過此限制,您可以將靜態匯入重寫為動態匯入,如下所示:

diff
callFunctionWithSideEffect()
- import { value } from './some/module.js'
+ const { value } = await import('./some/module.js')

執行 vitest 時,您可以使用 vi.hoisted 方法自動執行此操作。在底層,Vitest 會將靜態匯入轉換為動態匯入,並保留即時綁定。

diff
- callFunctionWithSideEffect()
import { value } from './some/module.js'
+ vi.hoisted(() => callFunctionWithSideEffect())

匯入不可用

在匯入之前執行程式碼意味著您無法存取匯入的變數,因為它們尚未定義:

ts
import { value } from './some/module.js';

vi.hoisted(() => { value }); // 拋出錯誤

此程式碼將產生錯誤:

Cannot access '__vi_import_0__' before initialization

如果您需要在 vi.hoisted 內部存取來自另一個模組的變數,請使用動態匯入:

ts
await vi.hoisted(async () => {
  const { value } = await import('./some/module.js');
});

然而,不鼓勵在 vi.hoisted 內部匯入任何東西,因為匯入已經被提升了——如果您需要在測試執行之前執行某些操作,只需在匯入的模組本身中執行即可。

此方法返回從 factory 返回的值。您可以在 vi.mock factory 中使用該值,如果您需要輕鬆存取本地定義的變數:

ts
import { expect, vi } from 'vitest';
import { originalMethod } from './path/to/module.js';

const { mockedMethod } = vi.hoisted(() => {
  return { mockedMethod: vi.fn() };
});

vi.mock('./path/to/module.js', () => {
  return { originalMethod: mockedMethod };
});

mockedMethod.mockReturnValue(100);
expect(originalMethod()).toBe(100);

請注意,即使您的環境不支援頂層 await,此方法也可以異步呼叫:

ts
const json = await vi.hoisted(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/posts');
  return response.json();
});

vi.setConfig ​

  • 類型:RuntimeConfig

更新當前測試檔案的配置。此方法僅支援會影響當前測試檔案的配置選項:

ts
vi.setConfig({
  allowOnly: true,
  testTimeout: 10_000,
  hookTimeout: 10_000,
  clearMocks: true,
  restoreMocks: true,
  fakeTimers: {
    now: new Date(2021, 11, 19),
    // 支援整個物件
  },
  maxConcurrency: 10,
  sequence: {
    hooks: 'stack',
    // 僅支援 "sequence.hooks"
  },
});

vi.resetConfig ​

  • 類型:RuntimeConfig

如果之前呼叫過 vi.setConfig,這將把配置重設為原始狀態。

Pager
上一頁模擬函式
下一頁expect

以 MIT 授權條款 發布。

版權所有 (c) 2021-Present Vitest Team

https://vitest.dev/api/vi

以 MIT 授權條款 發布。

版權所有 (c) 2021-Present Vitest Team