Contexto de prueba
Inspirado en Playwright Fixtures, el contexto de prueba de Vitest te permite definir utilidades, estados y fixtures que pueden ser utilizados en tus pruebas.
Uso
El primer argumento de cada callback de prueba es un contexto de prueba.
import { it } from 'vitest';
it('should work', ({ task }) => {
// imprime el nombre de la prueba
console.log(task.name);
});
Contexto de prueba incorporado
task
Un objeto de solo lectura que contiene metadatos sobre la prueba.
expect
La API expect
vinculada a la prueba actual:
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Esta API es útil para ejecutar pruebas de snapshot de manera concurrente, ya que el expect
global no puede rastrearlas:
import { it } from 'vitest';
it.concurrent('math is easy', ({ expect }) => {
expect(2 + 2).toMatchInlineSnapshot();
});
it.concurrent('math is hard', ({ expect }) => {
expect(2 * 2).toMatchInlineSnapshot();
});
skip
function skip(note?: string): never;
function skip(condition: boolean, note?: string): void;
Omite la ejecución subsiguiente de la prueba y la marca como omitida:
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Desde Vitest 3.1, acepta un parámetro booleano para omitir la prueba condicionalmente:
it('math is hard', ({ skip, mind }) => {
skip(mind === 'foggy');
expect(2 + 2).toBe(5);
});
annotate
3.2.0+
function annotate(
message: string,
attachment?: TestAttachment
): Promise<TestAnnotation>;
function annotate(
message: string,
type?: string,
attachment?: TestAttachment
): Promise<TestAnnotation>;
Agrega una anotación de prueba que será mostrada por tu reportero.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Un AbortSignal
que puede ser abortado por Vitest. La señal se aborta en estas situaciones:
- La prueba excede el tiempo límite.
- El usuario canceló manualmente la ejecución de la prueba con Ctrl+C.
- Se llamó programáticamente a
vitest.cancelCurrentRun
. - Otra prueba falló en paralelo y la bandera
bail
está configurada.
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
El hook onTestFailed
vinculado a la prueba actual. Esta API es útil si estás ejecutando pruebas concurrentemente y necesitas un manejo especial solo para esta prueba específica.
onTestFinished
El hook onTestFinished
vinculado a la prueba actual. Esta API es útil si estás ejecutando pruebas concurrentemente y necesitas un manejo especial solo para esta prueba específica.
Extender el contexto de prueba
Vitest proporciona dos formas diferentes para ayudarte a extender el contexto de prueba.
test.extend
Al igual que Playwright, puedes usar este método para definir tu propia API test
con fixtures personalizados y reutilizarla en cualquier lugar.
Por ejemplo, primero creamos el recolector test
con dos fixtures: todos
y archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// configura el fixture antes de cada función de prueba
todos.push(1, 2, 3);
// usa el valor del fixture
await use(todos);
// limpia el fixture después de cada función de prueba
todos.length = 0;
},
archive,
});
Luego podemos importarlo y usarlo.
import { expect } from 'vitest';
import { test } from './my-test.js';
test('add items to todos', ({ todos }) => {
expect(todos.length).toBe(3);
todos.push(4);
expect(todos.length).toBe(4);
});
test('move items from todos to archive', ({ todos, archive }) => {
expect(todos.length).toBe(3);
expect(archive.length).toBe(0);
archive.push(todos.pop());
expect(todos.length).toBe(2);
expect(archive.length).toBe(1);
});
También podemos añadir más fixtures o sobrescribir los existentes extendiendo nuestro test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Inicialización de fixtures
El runner de Vitest inicializará inteligentemente tus fixtures y los inyectará en el contexto de prueba basándose en el uso.
import { test as baseTest } from 'vitest';
const test = baseTest.extend<{
todos: number[];
archive: number[];
}>({
todos: async ({ task }, use) => {
await use([1, 2, 3]);
},
archive: [],
});
// todos no se ejecutará
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos se ejecutará
test('run', ({ todos }) => {});
WARNING
Al usar test.extend()
con fixtures, siempre debes usar el patrón de desestructuración de objetos { todos }
para acceder al contexto tanto en la función del fixture como en la de prueba.
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
Fixture automático
Vitest también soporta la sintaxis de tupla para fixtures, permitiéndote pasar opciones para cada fixture. Por ejemplo, puedes usarlo para inicializar explícitamente un fixture, incluso si no está siendo usado en las pruebas.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// esta función se ejecutará
setup();
await use();
teardown();
},
{ auto: true }, // Marcar como un fixture automático
],
});
test('works correctly');
Fixture predeterminado
Desde Vitest 3, puedes proporcionar diferentes valores en diferentes proyectos. Para habilitar esta característica, pasa { injected: true }
a las opciones. Si la clave no se especifica en la configuración del proyecto, se utilizará el valor predeterminado.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// valor predeterminado si "url" no está definido en la configuración
'/default',
// marca el fixture como "injected" para permitir la sobrescritura
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url es "/default" en "project-new"
// url es "/full" en "project-full"
// url es "/empty" en "project-empty"
});
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
projects: [
{
test: {
name: 'project-new',
},
},
{
test: {
name: 'project-full',
provide: {
url: '/full',
},
},
},
{
test: {
name: 'project-empty',
provide: {
url: '/empty',
},
},
},
],
},
});
Ámbito de valores por suite 3.1.0+
Desde Vitest 3.1, puedes sobrescribir los valores de contexto por suite y sus hijos usando la API test.scoped
:
import { test as baseTest, describe, expect } from 'vitest';
const test = baseTest.extend({
dependency: 'default',
dependant: ({ dependency }, use) => use({ dependency }),
});
describe('use scoped values', () => {
test.scoped({ dependency: 'new' });
test('uses scoped value', ({ dependant }) => {
// `dependant` usa el nuevo valor sobrescrito aplicado
// a todas las pruebas en esta suite
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// la suite anidada heredó el valor
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// la `dependency` está usando el valor predeterminado
// fuera de la suite con .scoped
expect(dependant).toEqual({ dependency: 'default' });
});
Esta API es especialmente útil si tienes un valor de contexto que depende de una variable dinámica, como una conexión a la base de datos:
const test = baseTest.extend<{
db: Database;
schema: string;
}>({
db: async ({ schema }, use) => {
const db = await createDb({ schema });
await use(db);
await cleanup(db);
},
schema: '',
});
describe('one type of schema', () => {
test.scoped({ schema: 'schema-1' });
// ... pruebas
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... pruebas
});
Contexto por ámbito 3.2.0+
Puedes definir un contexto que se iniciará una vez por archivo o por worker. Se inicia de la misma manera que un fixture regular con un parámetro de objetos:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
El valor se inicializa la primera vez que cualquier prueba lo ha accedido, a menos que las opciones del fixture tengan auto: true
- en este caso, el valor se inicializa antes de que se haya ejecutado cualquier prueba.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// siempre ejecuta este hook antes de cualquier prueba
auto: true,
},
],
});
El ámbito worker
ejecutará el fixture una vez por worker. El número de workers en ejecución depende de varios factores. Por defecto, cada archivo se ejecuta en un worker separado, por lo que los ámbitos file
y worker
funcionan de la misma manera.
Sin embargo, si deshabilitas el aislamiento, el número de workers está limitado por la configuración maxWorkers
o poolOptions
.
Ten en cuenta que especificar scope: 'worker'
al ejecutar pruebas en vmThreads
o vmForks
funcionará de la misma manera que scope: 'file'
. Esta limitación existe porque cada archivo de prueba tiene su propio contexto de VM, por lo que si Vitest lo iniciara una vez, un contexto podría filtrarse a otro y crear muchas inconsistencias de referencia (instancias de la misma clase harían referencia a diferentes constructores, por ejemplo).
TypeScript
Para proporcionar tipos de fixture para todos tus contextos personalizados, puedes pasar el tipo de fixtures como un genérico.
interface MyFixtures {
todos: number[];
archive: number[];
}
const test = baseTest.extend<MyFixtures>({
todos: [],
archive: [],
});
test('types are defined correctly', ({ todos, archive }) => {
expectTypeOf(todos).toEqualTypeOf<number[]>();
expectTypeOf(archive).toEqualTypeOf<number[]>();
});
Inferencia de tipos
Ten en cuenta que Vitest no soporta la inferencia de tipos cuando se llama a la función use
. Siempre es preferible pasar el tipo de contexto completo como el tipo genérico cuando se llama a test.extend
:
import { test as baseTest } from 'vitest';
const test = baseTest.extend<{
todos: number[];
schema: string;
}>({
todos: ({ schema }, use) => use([]),
schema: 'test',
});
test('types are correct', ({
todos, // number[]
schema, // string
}) => {
// ...
});
beforeEach
y afterEach
Obsoleto
Esta es una forma anticuada de extender el contexto y no funcionará cuando test
se extienda con test.extend
.
Los contextos son diferentes para cada prueba. Puedes acceder a ellos y extenderlos dentro de los hooks beforeEach
y afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// extiende el contexto
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Para proporcionar tipos de propiedades para todos tus contextos personalizados, puedes aumentar el tipo TestContext
añadiendo
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Si deseas proporcionar tipos de propiedades solo para hooks beforeEach
, afterEach
, it
y test
específicos, puedes pasar el tipo como un genérico.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context es 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo es 'string'
console.log(foo); // 'bar'
});