執行器 API
WARNING
這是一個高階 API。如果您只是想執行測試,可能不需要使用此 API。它主要供程式庫作者使用。
您可以在設定檔中使用 runner
選項指定測試執行器的路徑。此檔案應預設匯出一個類別建構函式,該建構函式實作以下方法:
export interface VitestRunner {
/**
* 在實際收集並執行測試之前,會最先呼叫此方法。
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* 在收集測試之後,以及 "onBeforeRun" 之前呼叫。
*/
onCollected?: (files: File[]) => unknown;
/**
* 當測試執行器應取消下一次測試執行時呼叫。
* 執行器應監聽此方法,並在被呼叫時,於
* "onBeforeRunSuite" 和 "onBeforeRunTask" 中將測試和套件標記為跳過。
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* 在執行單個測試之前呼叫。此時尚未產生 "result" 值。
*/
onBeforeRunTask?: (test: TaskPopulated) => unknown;
/**
* 在實際執行測試函式之前呼叫。此時已產生包含 "state" 和 "startTime" 的 "result"。
*/
onBeforeTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* 在設定結果和狀態之後呼叫。
*/
onAfterRunTask?: (test: TaskPopulated) => unknown;
/**
* 在執行測試函式之後立即呼叫。此時尚未產生新狀態。若測試函式拋出錯誤,則不會呼叫此方法。
*/
onAfterTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* 在執行單個套件之前被呼叫。此時尚未產生 "result" 值。
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* 在執行單個套件之後呼叫。此時已產生狀態和結果。
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* 若已定義,將取代 Vitest 預設的套件分割與處理方式。
* "before" 和 "after" 掛鉤將不會被忽略。
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* 若已定義,將取代 Vitest 預設的處理方式。如果您有自訂測試函式,這會很有用。
* "before" 和 "after" 掛鉤將不會被忽略。
*/
runTask?: (test: TaskPopulated) => Promise<void>;
/**
* 當任務更新時呼叫。與報告器中的 "onTaskUpdate" 相同,但此方法會在與測試相同的執行緒中執行。
*/
onTaskUpdate?: (
task: [string, TaskResult | undefined, TaskMeta | undefined][]
) => Promise<void>;
/**
* 在執行收集路徑中的所有測試之前呼叫。
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* 在執行收集路徑中的所有測試之後立即呼叫。
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* 當定義新的測試上下文時呼叫。如果您想向該上下文添加自訂屬性,這會很有用。
* 如果您只想透過執行器定義自訂上下文,請考慮使用 "setupFiles" 中的 "beforeAll" 來定義自訂上下文。
*/
extendTaskContext?: (context: TestContext) => TestContext;
/**
* 當匯入某些檔案時呼叫。此方法可在兩種情況下呼叫:收集測試,以及匯入設定檔。
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* 當 `test.extend` 與 `{ injected: true }` 一起使用時,執行器嘗試獲取其值時會呼叫此函式。
*/
injectValue?: (key: string) => unknown;
/**
* 公開可用的配置。
*/
config: VitestRunnerConfig;
/**
* 當前池的名稱。可能會影響伺服器端呼叫堆疊的推斷。
*/
pool?: string;
}
初始化此類別時,Vitest 會傳入其配置,您應將其作為 config
屬性公開:
import type { RunnerTestFile } from 'vitest';
import type { VitestRunner, VitestRunnerConfig } from 'vitest/suite';
import { VitestTestRunner } from 'vitest/runners';
class CustomRunner extends VitestTestRunner implements VitestRunner {
public config: VitestRunnerConfig;
constructor(config: VitestRunnerConfig) {
this.config = config;
}
onAfterRunFiles(files: RunnerTestFile[]) {
console.log('finished running', files);
}
}
export default CustomRunner;
WARNING
Vitest 也會將 ViteNodeRunner
的實例注入為 __vitest_executor
屬性。您可以使用它在 importFile
方法中處理檔案(這是 TestRunner
和 BenchmarkRunner
的預設行為)。
ViteNodeRunner
暴露了 executeId
方法,用於在 Vite 友善的環境中匯入測試檔案。這表示它會在執行時解析匯入並轉換檔案內容,以便 Node 能夠理解:
export default class Runner {
async importFile(filepath: string) {
await this.__vitest_executor.executeId(filepath);
}
}
WARNING
如果您沒有自訂執行器或未定義 runTask
方法,Vitest 將嘗試自動取得任務。如果您沒有使用 setFn
添加函式,則會失敗。
TIP
快照功能支援及其他一些功能取決於執行器。如果您不想失去這些功能,可以從 vitest/runners
匯入 VitestTestRunner
並擴展您的執行器。如果您想擴展效能基準測試功能,它也提供了 BenchmarkNodeRunner
。
任務
WARNING
「執行器任務 API」是實驗性的,主要應僅在測試執行期間使用。Vitest 也提供了「報告任務 API」(./api/test-module),在主執行緒中(例如在報告器內部)工作時應該優先考慮使用。
團隊目前正在討論未來是否應以「報告任務」取代「執行器任務」。
套件和測試在內部稱為 tasks
。Vitest 執行器在收集任何測試之前會初始化一個 File
任務——這是 Suite
的超集合,並帶有一些額外屬性。它在每個任務(包括 File
)上都可作為 file
屬性使用。
interface File extends Suite {
/**
* 檔案所屬的池名稱。
* @default 'forks'
*/
pool?: string;
/**
* 檔案的 UNIX 格式路徑。
*/
filepath: string;
/**
* 此檔案所屬的測試專案名稱。
*/
projectName: string | undefined;
/**
* 收集檔案中所有測試所花費的時間。
* 此時間也包含匯入所有檔案的依賴項。
*/
collectDuration?: number;
/**
* 匯入設定檔所花費的時間。
*/
setupDuration?: number;
}
每個套件都有一個 tasks
屬性,該屬性會在收集階段被填入。這有助於從上到下遍歷任務樹。
interface Suite extends TaskBase {
type: 'suite';
/**
* 檔案任務。它是此檔案的根任務。
*/
file: File;
/**
* 屬於此套件的任務陣列。
*/
tasks: Task[];
}
每個任務都有一個 suite
屬性,指向其所屬的套件。如果 test
或 describe
在頂層被初始化,它們將沒有 suite
屬性(它將不等於 file
!)。File
也從不具有 suite
屬性。這有助於從下到上遍歷任務。
interface Test<ExtraContext = object> extends TaskBase {
type: 'test';
/**
* 將傳遞給測試函式的上下文。
*/
context: TestContext & ExtraContext;
/**
* 檔案任務。它是此檔案的根任務。
*/
file: File;
/**
* 此任務是否因呼叫 `context.skip()` 而被跳過。
*/
pending?: boolean;
/**
* 此任務即使失敗是否仍應被視為成功。若任務失敗,它將被標記為通過。
*/
fails?: boolean;
/**
* 儲存 Promise(來自非同步期望),以便在測試完成前等待它們。
*/
promises?: Promise<any>[];
}
每個任務都可以包含 result
欄位。套件僅在套件回呼或 beforeAll
/afterAll
回呼中拋出錯誤,導致無法收集測試時,才會包含此欄位。測試在呼叫其回呼後總是包含此欄位——state
和 errors
欄位會根據結果是否存在。若在 beforeEach
或 afterEach
回呼中拋出錯誤,則該錯誤將存在於 task.result.errors
中。
export interface TaskResult {
/**
* 任務的狀態。在收集期間會繼承 `task.mode`。
* 當任務完成時,它將變為 `pass` 或 `fail`。
* - **pass**: 任務成功執行
* - **fail**: 任務失敗
*/
state: TaskState;
/**
* 任務執行期間發生的錯誤。若 `expect.soft()` 多次失敗,則可能有多個錯誤。
*/
errors?: ErrorWithDiff[];
/**
* 任務執行所花費的毫秒數。
*/
duration?: number;
/**
* 任務開始執行的毫秒時間戳。
*/
startTime?: number;
/**
* 任務完成後的堆記憶體大小(位元組)。
* 僅當設定 `logHeapUsage` 選項且定義 `process.memoryUsage` 時才可用。
*/
heap?: number;
/**
* 與此任務相關的掛鉤狀態。在報告時很有用。
*/
hooks?: Partial<
Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>
>;
/**
* 任務重試的次數。任務僅在失敗且設定 `retry` 選項時才會重試。
*/
retryCount?: number;
/**
* 任務重複的次數。任務僅在設定 `repeats` 選項時才會重複。此數字也包含 `retryCount`。
*/
repeatCount?: number;
}
您的任務函式
Vitest 提供了 createTaskCollector
工具,用於建立您自己的 test
方法。它的行為與測試相同,但在收集期間會呼叫一個自訂方法。
任務是套件中的一個物件。它會自動透過 suite.task
方法添加到當前套件中:
import { createTaskCollector, getCurrentSuite } from 'vitest/suite';
export { afterAll, beforeAll, describe } from 'vitest';
// 此函式將在收集階段被呼叫:
// 請勿在此處呼叫函式處理器,而是將其添加到套件任務中
// 使用 "getCurrentSuite().task()" 方法
// 注意:createTaskCollector 提供對 "todo"/"each"/... 的支援
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
getCurrentSuite().task(name, {
...this, // 以便正確追蹤 "todo"/"skip"/...
meta: {
customPropertyToDifferentiateTask: true,
},
handler: fn,
timeout,
});
});
import { afterAll, beforeAll, describe, myCustomTask } from './custom.js';
import { gardener } from './gardener.js';
describe('take care of the garden', () => {
beforeAll(() => {
gardener.putWorkingClothes();
});
myCustomTask('weed the grass', () => {
gardener.weedTheGrass();
});
myCustomTask.todo('mow the lawn', () => {
gardener.mowerTheLawn();
});
myCustomTask('water flowers', () => {
gardener.waterFlowers();
});
afterAll(() => {
gardener.goHome();
});
});
vitest ./garden/tasks.test.js