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

本頁導覽

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 不會模擬在 setup 檔案 中匯入的模組,因為在測試檔案執行時它們已經被快取。您可以在 vi.hoisted 中呼叫 vi.resetModules() 以在執行測試檔案之前清除所有模組快取。

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

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

自 Vitest 2.1 起,您也可以提供一個帶有 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 仍然使用字串而非模組物件進行操作。

但是,如果您使用 TypeScript 並在 tsconfig.json 中設定了 paths 別名,編譯器將無法正確解析匯入的類型。 為了使其正常工作,請確保將所有別名匯入替換為其對應的相對路徑。 例如,使用 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 將嘗試在專案的 root(預設為 process.cwd())中尋找 __mocks__ 資料夾。您可以透過 deps.moduleDirectories 設定選項告訴 Vitest 依賴項的位置。

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

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

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

ts
// increment.test.js
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
// ./increment.js
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
// example.ts
export function add(x: number, y: number): number {
  return x + y;
}

export function fetchSomething(): Promise<Response> {
  return fetch('https://vitest.dev/');
}
ts
// example.test.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 axios = await vi.importActual('./example.js');

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

vi.importMock ​

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

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

vi.unmock ​

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

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

vi.doUnmock ​

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

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

ts
// ./increment.js
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 測試中不會被重新評估

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

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

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

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

建立一個函數的 spy,儘管可以在沒有函數的情況下初始化。每次呼叫函數時,它都會儲存其呼叫參數、返回值和實例。此外,您可以使用 方法 操作其行為。 如果未提供函數,則在呼叫時模擬將返回 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.isMockFunction ​

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

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

vi.clearAllMocks ​

將在所有 spies 上呼叫 .mockClear()。這將清除模擬歷史記錄,但不會將其實作重設為預設的實作。

vi.resetAllMocks ​

將在所有 spies 上呼叫 .mockReset()。這將清除模擬歷史記錄並將其實作重設為空函數(將返回 undefined)。

vi.restoreAllMocks ​

將在所有 spies 上呼叫 .mockRestore()。這將清除模擬歷史記錄並將其實作重設為原始的實作。

vi.spyOn ​

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

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

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

您可以在 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

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

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 環境中可以 spy 匯出,但這在未來可能會改變。

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

返回使用 setSystemTime 設定的模擬當前日期。如果日期未被模擬,此方法將返回 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()。

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

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

TIP

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

vi.isFakeTimers ​

  • 類型: () => boolean

如果啟用了偽造計時器,則返回 true。

vi.useRealTimers ​

  • 類型: () => Vitest

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

其他 ​

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

vi.waitFor ​

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

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

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

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 方法自動完成此操作。

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

此方法返回 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 promised = 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) 2024 Mithril Contributors

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

以 MIT 授權條款 發布。

版權所有 (c) 2024 Mithril Contributors