Exécuteur de tests
WARNING
Il s'agit d'une API avancée. Si vous souhaitez simplement exécuter des tests, vous n'en aurez probablement pas besoin. Elle est principalement destinée aux auteurs de bibliothèques.
Vous pouvez spécifier le chemin vers votre exécuteur de tests avec l'option runner
dans votre fichier de configuration. Ce fichier doit comporter une exportation par défaut avec un constructeur de classe implémentant ces méthodes :
export interface VitestRunner {
/**
* Première méthode appelée avant la collecte et l'exécution des tests.
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* Appelée après la collecte des tests et avant "onBeforeRun".
*/
onCollected?: (files: File[]) => unknown;
/**
* Appelée lorsque l'exécuteur de tests doit annuler les exécutions de tests suivantes.
* L'exécuteur doit réagir à cette méthode et marquer les tests et les suites comme ignorés dans
* "onBeforeRunSuite" et "onBeforeRunTask" lorsqu'elle est appelée.
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* Appelée avant l'exécution d'un test individuel. Le champ "result" n'est pas encore disponible.
*/
onBeforeRunTest?: (test: TaskPopulated) => unknown;
/**
* Appelée avant l'exécution effective de la fonction de test. Le champ "result" est déjà disponible avec "state" et "startTime".
*/
onBeforeTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Appelée une fois le résultat et l'état définis.
*/
onAfterRunTask?: (test: TaskPopulated) => unknown;
/**
* Appelée immédiatement après l'exécution de la fonction de test. Le nouvel état n'est pas encore disponible. Ne sera pas appelée si la fonction de test lève une exception.
*/
onAfterTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Appelée avant l'exécution d'une suite individuelle. Le champ "result" n'est pas encore disponible.
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* Appelée après l'exécution d'une suite individuelle. L'état et le résultat sont disponibles.
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* Si définie, sera appelée à la place de la gestion et de la répartition habituelles des suites Vitest.
* Les hooks "before" et "after" ne seront pas ignorés.
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* Si définie, sera appelée à la place de la gestion habituelle de Vitest. Utile si vous disposez de votre propre fonction de test personnalisée.
* Les hooks "before" et "after" ne seront pas ignorés.
*/
runTask?: (test: TaskPopulated) => Promise<void>;
/**
* Appelée lorsqu'une tâche est mise à jour. Cette méthode est similaire à "onTaskUpdate" dans un rapporteur, mais s'exécute dans le même thread que les tests.
*/
onTaskUpdate?: (task: [string, TaskResult | undefined][]) => Promise<void>;
/**
* Appelée avant l'exécution de tous les tests dans les chemins collectés.
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* Appelée immédiatement après l'exécution de tous les tests dans les chemins collectés.
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* Appelée lorsqu'un nouveau contexte est défini pour un test. Utile si vous souhaitez ajouter des propriétés personnalisées au contexte.
* Si vous souhaitez uniquement définir un contexte personnalisé à l'aide d'un exécuteur, envisagez plutôt d'utiliser "beforeAll" dans "setupFiles".
*
* Cette méthode est appelée pour les gestionnaires "test" et "custom".
*
* @see https://www.getbook.com/en/book/vitest-2/advanced/runner#your-task-function
*/
extendTaskContext?: <T extends Test | Custom>(
context: TaskContext<T>
) => TaskContext<T>;
/**
* Appelée lorsque certains fichiers sont importés. Cette méthode peut être appelée dans deux situations : lors de la collecte des tests et lors de l'importation des fichiers de configuration.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* Configuration publiquement disponible.
*/
config: VitestRunnerConfig;
}
Lors de l'initialisation de cette classe, Vitest transmet sa configuration. Vous devez l'exposer en tant que propriété config
.
WARNING
Vitest injecte également une instance de ViteNodeRunner
en tant que propriété __vitest_executor
. Vous pouvez l'utiliser pour traiter les fichiers dans la méthode importFile
(c'est le comportement par défaut de TestRunner
et BenchmarkRunner
).
ViteNodeRunner
expose la méthode executeId
, qui est utilisée pour importer les fichiers de test dans un environnement compatible avec Vite. Cela signifie qu'il résoudra les importations et transformera le contenu du fichier à l'exécution afin que Node puisse le comprendre.
TIP
Le support des snapshots et certaines autres fonctionnalités dépendent de l'exécuteur. Si vous ne voulez pas perdre ces fonctionnalités, vous pouvez étendre votre exécuteur à partir de VitestTestRunner
importé de vitest/runners
. Il expose également BenchmarkNodeRunner
si vous souhaitez étendre les fonctionnalités de benchmark.
Tâches
Les suites et les tests sont appelés tasks
en interne. L'exécuteur Vitest initialise une tâche File
avant de collecter les tests - c'est un sur-ensemble de Suite
avec quelques propriétés supplémentaires. Elle est disponible sur chaque tâche (y compris File
) en tant que propriété file
.
interface File extends Suite {
/**
* Le nom du pool auquel le fichier appartient.
* @default 'forks'
*/
pool?: string;
/**
* Le chemin d'accès au fichier au format UNIX.
*/
filepath: string;
/**
* Le nom du projet d'espace de travail auquel le fichier appartient.
*/
projectName: string | undefined;
/**
* Le temps qu'il a fallu pour collecter tous les tests dans le fichier.
* Ce temps inclut également l'importation de toutes les dépendances du fichier.
*/
collectDuration?: number;
/**
* Le temps qu'il a fallu pour importer le fichier de configuration.
*/
setupDuration?: number;
/**
* Indique si le fichier est initialisé sans exécuter de tests.
* Cela permet de peupler l'état côté serveur par Vitest.
*/
local?: boolean;
}
Chaque suite a une propriété tasks
qui est peuplée pendant la phase de collecte. Il est utile de parcourir l'arbre des tâches de haut en bas.
interface Suite extends TaskBase {
type: 'suite';
/**
* Tâche de fichier. C'est la tâche racine du fichier.
*/
file: File;
/**
* Un tableau de tâches qui font partie de la suite.
*/
tasks: Task[];
}
Chaque tâche a une propriété suite
qui référence la suite dans laquelle elle se trouve. Si test
ou describe
sont initialisés au niveau supérieur, ils n'auront pas de propriété suite
(elle ne sera pas égale à file
!). File
n'a jamais non plus de propriété suite
. Il est utile de parcourir les tâches de bas en haut.
interface Test<ExtraContext = object> extends TaskBase {
type: 'test';
/**
* Contexte de test qui sera passé à la fonction de test.
*/
context: TaskContext<Test> & ExtraContext & TestContext;
/**
* Tâche de fichier. C'est la tâche racine du fichier.
*/
file: File;
/**
* Indique si la tâche a été ignorée en appelant `t.skip()`.
*/
pending?: boolean;
/**
* Indique si la tâche doit réussir si elle échoue. Si la tâche échoue, elle sera marquée comme réussie.
*/
fails?: boolean;
/**
* Hooks qui s'exécuteront si la tâche échoue. L'ordre dépend de l'option `sequence.hooks`.
*/
onFailed?: OnTestFailedHandler[];
/**
* Hooks qui s'exécuteront après la fin de la tâche. L'ordre dépend de l'option `sequence.hooks`.
*/
onFinished?: OnTestFinishedHandler[];
/**
* Conserve les promesses (provenant des attentes asynchrones) pour les attendre avant de terminer le test.
*/
promises?: Promise<any>[];
}
Chaque tâche peut avoir un champ result
. Les suites ne peuvent avoir ce champ que si une erreur levée dans un callback de suite ou dans les callbacks beforeAll
/afterAll
les empêche de collecter les tests. Les tests ont toujours ce champ après l'appel de leurs callbacks - les champs state
et errors
sont présents en fonction du résultat. Si une erreur a été levée dans les callbacks beforeEach
ou afterEach
, l'erreur levée sera présente dans task.result.errors
.
export interface TaskResult {
/**
* État de la tâche. Hérite du `task.mode` pendant la collecte.
* Lorsque la tâche est terminée, cet état sera changé en `pass` ou `fail`.
* - **pass**: la tâche s'est exécutée avec succès
* - **fail**: la tâche a échoué
*/
state: TaskState;
/**
* Erreurs survenues pendant l'exécution de la tâche. Il est possible d'avoir plusieurs erreurs
* si `expect.soft()` a échoué plusieurs fois.
*/
errors?: ErrorWithDiff[];
/**
* Durée d'exécution de la tâche en millisecondes.
*/
duration?: number;
/**
* Heure de début de l'exécution de la tâche en millisecondes.
*/
startTime?: number;
/**
* Taille du tas en octets après la fin de la tâche.
* Disponible uniquement si l'option `logHeapUsage` est définie et que `process.memoryUsage` est défini.
*/
heap?: number;
/**
* État des hooks associés à cette tâche. Utile lors de la génération de rapports.
*/
hooks?: Partial<
Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>
>;
/**
* Le nombre de fois où la tâche a été réessayée. La tâche n'est réessayée que si elle
* a échoué et que l'option `retry` est définie.
*/
retryCount?: number;
/**
* Le nombre de fois que la tâche a été répétée. La tâche n'est répétée que si
* l'option `repeats` est définie. Ce nombre comprend également `retryCount`.
*/
repeatCount?: number;
}
Votre fonction de tâche
Vitest expose un type de tâche Custom
qui permet aux utilisateurs de réutiliser les rapporteurs intégrés. Il est pratiquement identique à Test
, mais son type est 'custom'
.
Une tâche est un objet qui fait partie d'une suite. Elle est automatiquement ajoutée à la suite actuelle avec la méthode suite.task
:
// ./utils/custom.js
import { createTaskCollector, getCurrentSuite, setFn } from 'vitest/suite';
export { afterAll, beforeAll, describe } from 'vitest';
// cette fonction sera appelée pendant la phase de collecte :
// ne pas appeler le gestionnaire de fonction ici, ajoutez-le aux tâches de la suite
// avec la méthode "getCurrentSuite().task()"
// note : createTaskCollector prend en charge "todo"/"each"/...
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
getCurrentSuite().task(name, {
...this, // pour que "todo"/"skip"/... soit correctement suivi
meta: {
customPropertyToDifferentiateTask: true,
},
handler: fn,
timeout,
});
});
// ./garden/tasks.test.js
import { afterAll, beforeAll, describe, myCustomTask } from '../custom.js';
import { gardener } from './gardener.js';
describe('prendre soin du jardin', () => {
beforeAll(() => {
gardener.putWorkingClothes();
});
myCustomTask('désherber la pelouse', () => {
gardener.weedTheGrass();
});
myCustomTask.todo('tondre la pelouse', () => {
gardener.mowerTheLawn();
});
myCustomTask('arroser les fleurs', () => {
gardener.waterFlowers();
});
afterAll(() => {
gardener.goHome();
});
});
vitest ./garden/tasks.test.js
WARNING
Si vous ne disposez pas d'un exécuteur personnalisé ou si vous n'avez pas défini la méthode runTest
, Vitest essaiera de récupérer une tâche automatiquement. Si vous n'avez pas ajouté de fonction avec setFn
, cela échouera.