API del Runner
WARNING
Si tratta di un'API avanzata. Se vuoi solo eseguire i test, probabilmente non ne hai bisogno. È utilizzata principalmente dagli autori di librerie.
Puoi specificare il percorso del tuo test runner con l'opzione runner
nel tuo file di configurazione. Questo file dovrebbe avere un'esportazione di default con un costruttore di classe che implementa i seguenti metodi:
export interface VitestRunner {
/**
* La prima funzione che viene chiamata prima di raccogliere ed eseguire i test.
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* Viene chiamato dopo la raccolta dei test e prima di "onBeforeRun".
*/
onCollected?: (files: File[]) => unknown;
/**
* Chiamato quando il test runner dovrebbe annullare le prossime esecuzioni di test.
* Il runner dovrebbe intercettare questo metodo e contrassegnare i test e le suite come saltati in
* "onBeforeRunSuite" e "onBeforeRunTask" quando chiamato.
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* Chiamato prima di eseguire un singolo test. Non contiene ancora il "result".
*/
onBeforeRunTask?: (test: TaskPopulated) => unknown;
/**
* Chiamato prima di eseguire effettivamente la funzione di test. Contiene già il "result" con "state" e "startTime".
*/
onBeforeTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Chiamato dopo che il risultato e lo stato sono stati impostati.
*/
onAfterRunTask?: (test: TaskPopulated) => unknown;
/**
* Chiamato immediatamente dopo l'esecuzione della funzione di test. Lo stato non è ancora aggiornato. Non viene chiamato se la funzione di test genera un errore.
*/
onAfterTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Chiamato prima di eseguire una singola suite. Non ha ancora "result".
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* Chiamato dopo aver eseguito una singola suite. Ha stato e risultato.
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* Se definito, verrà chiamato al posto della normale partizione e gestione della suite di Vitest.
* I hook "before" e "after" non saranno ignorati.
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* Se definito, verrà chiamato al posto della normale gestione di Vitest. Utile per funzioni di test personalizzate.
* I hook "before" e "after" non saranno ignorati.
*/
runTask?: (test: TaskPopulated) => Promise<void>;
/**
* Chiamato quando un task viene aggiornato. Analogo a "onTaskUpdate" in un reporter, ma eseguito nello stesso thread dei test.
*/
onTaskUpdate?: (
task: [string, TaskResult | undefined, TaskMeta | undefined][]
) => Promise<void>;
/**
* Chiamato prima di eseguire tutti i test nei percorsi raccolti.
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* Chiamato subito dopo aver eseguito tutti i test nei percorsi raccolti.
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* Chiamato quando viene definito un nuovo contesto per un test. Utile se si desidera aggiungere proprietà personalizzate al contesto.
* Se si desidera solo definire un contesto personalizzato con un runner, si consiglia di utilizzare "beforeAll" in "setupFiles".
*/
extendTaskContext?: (context: TestContext) => TestContext;
/**
* Chiamato quando vengono importati determinati file. Può essere chiamato in due situazioni: per raccogliere test e per importare file di setup.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* Funzione chiamata quando il runner tenta di ottenere il valore quando `test.extend` è usato con `{ injected: true }`
*/
injectValue?: (key: string) => unknown;
/**
* Configurazione pubblicamente accessibile.
*/
config: VitestRunnerConfig;
/**
* Il nome del pool corrente. Può influenzare il modo in cui la traccia dello stack viene inferita lato server.
*/
pool?: string;
}
Quando si inizializza questa classe, Vitest passa la configurazione di Vitest - dovresti esporla come proprietà 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 fornisce anche un'istanza di ViteNodeRunner
nella proprietà __vitest_executor
. Puoi usarla per elaborare i file nel metodo importFile
(questo è il comportamento predefinito di TestRunner
e BenchmarkRunner
).
ViteNodeRunner
offre il metodo executeId
, che viene utilizzato per importare i file di test in un ambiente compatibile con Vite. Ciò significa che risolverà le importazioni e trasformerà il contenuto del file in fase di esecuzione affinché Node possa interpretarlo:
export default class Runner {
async importFile(filepath: string) {
await this.__vitest_executor.executeId(filepath);
}
}
WARNING
Se non hai un runner personalizzato o non hai definito il metodo runTask
, Vitest tenterà di gestire un task automaticamente. Se non hai aggiunto una funzione con setFn
, l'operazione fallirà.
TIP
La gestione degli snapshot e alcune altre funzionalità dipendono dal runner. Se non vuoi perdere queste funzionalità, puoi estendere il tuo runner da VitestTestRunner
importato da vitest/runners
. Espone anche BenchmarkNodeRunner
, se vuoi estendere la funzionalità di benchmark.
Task
WARNING
L'API Runner Tasks è sperimentale e dovrebbe essere utilizzata principalmente solo nel runtime dei test. Vitest espone anche l'"API Reported Tasks", che dovrebbe essere preferita quando si lavora nel thread principale (all'interno del reporter, ad esempio).
Il team sta attualmente discutendo se "Runner Tasks" dovrebbe essere sostituito da "Reported Tasks" in futuro.
Internamente, suite e test sono denominati tasks
. Il runner di Vitest inizializza un task File
prima di raccogliere qualsiasi test - questo è un superset di Suite
con alcune proprietà aggiuntive. È disponibile su ogni task (incluso File
) come proprietà file
.
interface File extends Suite {
/**
* Il nome del pool a cui il file appartiene.
* @default 'forks'
*/
pool?: string;
/**
* Il percorso del file in formato UNIX.
*/
filepath: string;
/**
* Il nome del progetto di test a cui appartiene il file.
*/
projectName: string | undefined;
/**
* Il tempo impiegato per raccogliere tutti i test nel file.
* Questo tempo comprende anche l'importazione di tutte le dipendenze del file.
*/
collectDuration?: number;
/**
* Il tempo impiegato per importare il file di setup.
*/
setupDuration?: number;
}
Ogni suite ha una proprietà tasks
che viene popolata durante la fase di raccolta. È utile per attraversare l'albero dei task dall'alto verso il basso.
interface Suite extends TaskBase {
type: 'suite';
/**
* Il task del file. È il task radice del file.
*/
file: File;
/**
* Un array di task che appartengono alla suite.
*/
tasks: Task[];
}
Ogni task ha una proprietà suite
che fa riferimento alla suite in cui si trova. Se test
o describe
vengono inizializzati al livello superiore, non avranno una proprietà suite
(e non sarà uguale a file
!). Anche File
non ha mai una proprietà suite
. È utile percorrere i task dal basso verso l'alto.
interface Test<ExtraContext = object> extends TaskBase {
type: 'test';
/**
* Contesto del test che verrà passato alla funzione di test.
*/
context: TestContext & ExtraContext;
/**
* Task del file. È il task radice del file.
*/
file: File;
/**
* Se il task è stato saltato chiamando `context.skip()`.
*/
pending?: boolean;
/**
* Se il task deve essere considerato riuscito anche in caso di fallimento. Se il task fallisce, verrà comunque contrassegnato come superato.
*/
fails?: boolean;
/**
* Conserva le promesse (da aspettative asincrone) per attenderne il completamento prima di terminare il test.
*/
promises?: Promise<any>[];
}
Ogni task può avere un campo result
. Le suite possono avere questo campo solo se un errore generato all'interno di un callback di suite o di callback beforeAll
/afterAll
impedisce la raccolta dei test. I test hanno sempre questo campo dopo che i loro callback sono stati chiamati - i campi state
ed errors
vengono popolati in base all'esito. Se un errore è stato generato nei callback beforeEach
o afterEach
, l'errore generato sarà presente in task.result.errors
.
export interface TaskResult {
/**
* Stato del task. Eredita il `task.mode` durante la fase di raccolta.
* Quando il task è terminato, verrà cambiato in `pass` o `fail`.
* - **pass**: task eseguito con successo
* - **fail**: task fallito
*/
state: TaskState;
/**
* Errori che si sono verificati durante l'esecuzione del task. Possono verificarsi più errori
* se `expect.soft()` ha fallito più volte.
*/
errors?: ErrorWithDiff[];
/**
* Durata del task in millisecondi.
*/
duration?: number;
/**
* Ora di inizio del task in millisecondi.
*/
startTime?: number;
/**
* Dimensione dell'heap in byte dopo che il task è terminato.
* Disponibile solo se l'opzione `logHeapUsage` è impostata e `process.memoryUsage` è definito.
*/
heap?: number;
/**
* Stato dei hook relativi a questo task. Utile per la segnalazione.
*/
hooks?: Partial<
Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>
>;
/**
* Il numero di volte in cui il task è stato ritentato. Il task viene ritentato solo in caso di fallimento e se l'opzione `retry` è impostata.
*/
retryCount?: number;
/**
* Il numero di volte in cui il task è stato ripetuto. Il task viene ripetuto solo se
* l'opzione `repeats` è impostata. Questo conteggio include anche `retryCount`.
*/
repeatCount?: number;
}
La tua funzione Task
Vitest espone l'utility createTaskCollector
per creare il tuo metodo test
. Si comporta allo stesso modo di un test, ma invoca un metodo personalizzato durante la fase di raccolta.
Un task è un oggetto che fa parte di una suite. Viene automaticamente aggiunto alla suite corrente tramite il metodo suite.task
:
import { createTaskCollector, getCurrentSuite } from 'vitest/suite';
export { afterAll, beforeAll, describe } from 'vitest';
// questa funzione verrà chiamata durante la fase di raccolta:
// non invocare qui la funzione handler, aggiungila ai task della suite
// con il metodo "getCurrentSuite().task()"
// nota: createTaskCollector supporta "todo"/"each"/...
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
getCurrentSuite().task(name, {
...this, // in modo che "todo"/"skip"/... venga tracciato correttamente
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