API del Runner
WARNING
Esta es una API avanzada. Si solo necesitas ejecutar pruebas, es probable que no la necesites. Se utiliza principalmente por autores de bibliotecas.
Puedes especificar la ruta a tu ejecutor de pruebas (runner) con la opción runner
en tu archivo de configuración. Este archivo debe tener una exportación por defecto con un constructor de clase que implemente los siguientes métodos:
export interface VitestRunner {
/**
* Es lo primero que se invoca antes de recolectar y ejecutar las pruebas.
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* Se llama después de recolectar las pruebas y antes de "onBeforeRunFiles".
*/
onCollected?: (files: File[]) => unknown;
/**
* Se llama cuando el ejecutor de pruebas debe cancelar las próximas ejecuciones.
* El ejecutor debe atender este método y marcar las pruebas y suites como omitidas en
* "onBeforeRunSuite" y "onBeforeRunTask" cuando se invoca.
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* Se llama antes de ejecutar una tarea individual (prueba o suite). Aún no contiene "result".
*/
onBeforeRunTask?: (test: TaskPopulated) => unknown;
/**
* Se llama antes de ejecutar la función de prueba. Ya incluye "result" con "state" y "startTime".
*/
onBeforeTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Se llama después de que se establecen el resultado y el estado de una tarea.
*/
onAfterRunTask?: (test: TaskPopulated) => unknown;
/**
* Se llama justo después de ejecutar la función de prueba. Aún no se ha establecido un nuevo estado. No se llamará si la función de prueba genera un error.
*/
onAfterTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Se llama antes de ejecutar una suite individual. Aún no contiene "result".
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* Se llama después de ejecutar una suite individual. Ya tiene estado y resultado.
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* Si se define, se llamará en lugar de la partición y manejo estándar de la suite de Vitest.
* Los hooks "before" y "after" se ejecutarán.
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* Si se define, se llamará en lugar del manejo habitual de Vitest para una tarea. Útil si tienes tu propia función de prueba personalizada.
* Los hooks "before" y "after" se ejecutarán.
*/
runTask?: (test: TaskPopulated) => Promise<void>;
/**
* Se llama cuando se actualiza una tarea. Equivale a "onTaskUpdate" en un reportero, pero esto se ejecuta en el mismo hilo que las pruebas.
*/
onTaskUpdate?: (
task: [string, TaskResult | undefined, TaskMeta | undefined][]
) => Promise<void>;
/**
* Se llama antes de ejecutar todas las pruebas en los archivos recolectados.
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* Se llama justo después de ejecutar todas las pruebas en los archivos recolectados.
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* Se llama cuando se define un nuevo contexto para una prueba. Útil para añadir propiedades personalizadas al contexto.
* Si solo quieres definir un contexto personalizado con un ejecutor, considera usar "beforeAll" en "setupFiles" en su lugar.
*/
extendTaskContext?: (context: TestContext) => TestContext;
/**
* Se invoca al importar ciertos archivos. Puede llamarse en dos situaciones: para recolectar pruebas y para importar archivos de configuración.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* Función que se invoca cuando el runner intenta obtener el valor al usar `test.extend` con `{ injected: true }`
*/
injectValue?: (key: string) => unknown;
/**
* Configuración disponible públicamente.
*/
config: VitestRunnerConfig;
/**
* El nombre del pool actual. Puede afectar cómo se infiere el seguimiento de la pila en el lado del servidor.
*/
pool?: string;
}
Al instanciar esta clase, Vitest pasa la configuración de Vitest, la cual debe exponerse como una propiedad 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 también inyecta una instancia de ViteNodeRunner
como propiedad __vitest_executor
. Esta puede utilizarse para procesar archivos en el método importFile
(este es el comportamiento predeterminado de TestRunner
y BenchmarkRunner
).
ViteNodeRunner
expone el método executeId
, que se utiliza para importar archivos de prueba en un entorno compatible con Vite. Esto significa que resolverá las importaciones y transformará el contenido del archivo en tiempo de ejecución para que Node pueda entenderlo:
export default class Runner {
async importFile(filepath: string) {
await this.__vitest_executor.executeId(filepath);
}
}
WARNING
Si no se dispone de un runner personalizado o no se ha definido el método runTask
, Vitest intentará obtener una tarea automáticamente. Si no se ha añadido una función con setFn
, la operación fallará.
TIP
El soporte de instantáneas (snapshots) y otras características dependen del runner. Para no perder esta funcionalidad, se puede extender el runner desde VitestTestRunner
importado de vitest/runners
. También expone BenchmarkNodeRunner
, si se desea extender la funcionalidad de benchmark.
Tareas
WARNING
La "API de Tareas del Ejecutor" es experimental y debe utilizarse principalmente durante la ejecución de pruebas. Vitest también expone la "API de Tareas Reportadas", que debe preferirse cuando se trabaja en el hilo principal (dentro del reportero, por ejemplo).
El equipo está discutiendo actualmente si las "Tareas del Ejecutor" deberían ser reemplazadas por las "Tareas Reportadas" en el futuro.
Internamente, las suites y las pruebas se denominan tasks
. El runner de Vitest inicia una tarea File
antes de recolectar cualquier prueba; esta tarea es un superconjunto de Suite
con propiedades adicionales. Está disponible en cada tarea (incluido File
) como una propiedad file
.
interface File extends Suite {
/**
* El nombre del pool al que pertenece el archivo.
* @default 'forks'
*/
pool?: string;
/**
* La ruta del archivo en formato UNIX.
*/
filepath: string;
/**
* El nombre del proyecto de prueba asociado al archivo.
*/
projectName: string | undefined;
/**
* El tiempo que tomó recolectar todas las pruebas del archivo.
* Este tiempo también incluye la importación de todas las dependencias del archivo.
*/
collectDuration?: number;
/**
* El tiempo que tardó en importar el archivo de configuración.
*/
setupDuration?: number;
}
Cada suite posee una propiedad tasks
que se completa durante la fase de recolección. Esto es útil para recorrer el árbol de tareas de arriba hacia abajo.
interface Suite extends TaskBase {
type: 'suite';
/**
* La tarea de tipo "File". Es la tarea raíz del archivo.
*/
file: File;
/**
* Un arreglo de tareas que forman parte de la suite.
*/
tasks: Task[];
}
Cada tarea tiene una propiedad suite
que hace referencia a la suite en la que se encuentra. Si test
o describe
se inician en el nivel superior, no tendrán una propiedad suite
(¡no será igual a file
!). File
tampoco tiene nunca una propiedad suite
. Es útil para recorrer las tareas de abajo hacia arriba.
interface Test<ExtraContext = object> extends TaskBase {
type: 'test';
/**
* Contexto de prueba que se pasará a la función de prueba.
*/
context: TestContext & ExtraContext;
/**
* Tarea de archivo. Es la tarea raíz del archivo.
*/
file: File;
/**
* Si la tarea fue omitida llamando a `context.skip()`.
*/
pending?: boolean;
/**
* Si la tarea debe considerarse exitosa aunque falle. Si la tarea falla, se marcará como pasada.
*/
fails?: boolean;
/**
* Guarda promesas (de expectativas asíncronas) para esperarlas antes de finalizar la prueba.
*/
promises?: Promise<any>[];
}
Cada tarea puede tener un campo result
. Las suites solo lo tendrán si un error lanzado dentro de un callback de suite o de los callbacks beforeAll
/afterAll
impide la recolección de pruebas. Las pruebas siempre incluyen este campo tras ejecutar sus callbacks; los campos state
y errors
estarán presentes según el resultado. Si se produce un error en los callbacks beforeEach
o afterEach
, dicho error estará presente en task.result.errors
.
export interface TaskResult {
/**
* Estado de la tarea. Adquiere el `task.mode` durante la recolección.
* Una vez finalizada la tarea, su estado cambiará a `pass` o `fail`.
* - **pass**: la tarea se completó correctamente
* - **fail**: la tarea falló
*/
state: TaskState;
/**
* Errores ocurridos durante la ejecución de la tarea. Pueden presentarse múltiples errores
* si `expect.soft()` falló varias veces.
*/
errors?: ErrorWithDiff[];
/**
* Tiempo en milisegundos que tomó ejecutar la tarea.
*/
duration?: number;
/**
* Tiempo en milisegundos en que la tarea comenzó a ejecutarse.
*/
startTime?: number;
/**
* Tamaño del montículo (heap) en bytes después de que la tarea terminó.
* Solo disponible si la opción `logHeapUsage` está habilitada y `process.memoryUsage` está definido.
*/
heap?: number;
/**
* Estado de los hooks relacionados con esta tarea. Resulta útil para la generación de informes.
*/
hooks?: Partial<
Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>
>;
/**
* Número de veces que se reintentó la tarea. La tarea solo se reintenta si
* falló y la opción `retry` está configurada.
*/
retryCount?: number;
/**
* Número de veces que se repitió la tarea. La tarea solo se repite si
* la opción `repeats` está configurada. Este número incluye también `retryCount`.
*/
repeatCount?: number;
}
Tu Función de Tarea
Vitest expone la utilidad createTaskCollector
para crear un método test
personalizado. Se comporta de la misma manera que una prueba, pero invoca un método personalizado durante la recolección.
Una tarea es un objeto que forma parte de una suite. Se agrega automáticamente a la suite actual mediante el método suite.task
:
import { createTaskCollector, getCurrentSuite } from 'vitest/suite';
export { afterAll, beforeAll, describe } from 'vitest';
// esta función se llamará durante la fase de recolección:
// no invocar el manejador de la función aquí; agregarlo a las tareas de la suite
// con el método "getCurrentSuite().task()"
// nota: createTaskCollector incluye soporte para "todo"/"each"/...
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
getCurrentSuite().task(name, {
...this, // para un seguimiento correcto de "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