Contexto de Testes
Inspirado nos Playwright Fixtures, o contexto de teste do Vitest permite que você defina utilitários, estados e fixtures que podem ser usados em seus testes.
Uso
O primeiro argumento de cada callback de teste é o contexto de teste.
import { it } from 'vitest';
it('should work', ({ task }) => {
// imprime o nome do teste
console.log(task.name);
});
Contexto de Teste Embutido
task
É um objeto somente leitura que contém metadados do teste.
expect
A API expect
está vinculada ao teste atual:
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Esta API é útil para executar snapshots de teste em paralelo, pois o expect global não consegue rastreá-los:
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;
Pula a execução do teste subsequente e o marca como ignorado:
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Desde o Vitest 3.1, aceita um parâmetro booleano para ignorar o teste de forma condicional:
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>;
Adicione uma anotação de teste que será exibida pelo seu relator.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Um AbortSignal
que pode ser abortado pelo Vitest. O sinal é abortado nestas situações:
- O teste excede o tempo limite
- O usuário cancelou manualmente a execução do teste com Ctrl+C
vitest.cancelCurrentRun
foi invocado programaticamente- Outro teste falhou em paralelo e a flag
bail
está definida
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
O hook onTestFailed
associado ao teste atual. Esta API é útil se você estiver executando testes concorrentemente e precisar de um tratamento especial apenas para este teste específico.
onTestFinished
O hook onTestFinished
associado ao teste atual. Esta API é útil se você estiver executando testes concorrentemente e precisar de um tratamento especial apenas para este teste específico.
Estender Contexto de Teste
O Vitest oferece duas maneiras diferentes para ajudar você a estender o contexto de teste.
test.extend
Assim como o Playwright, você pode usar este método para definir sua própria API test
com fixtures personalizadas e reutilizá-la em qualquer lugar.
Por exemplo, primeiro criamos o coletor test
com duas fixtures: todos
e archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// configura a fixture antes de cada função de teste
todos.push(1, 2, 3);
// usa o valor da fixture
await use(todos);
// limpa a fixture após cada função de teste
todos.length = 0;
},
archive,
});
Então podemos importá-lo e usá-lo.
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);
});
Também podemos adicionar mais fixtures ou sobrescrever fixtures existentes estendendo nosso test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Inicialização de Fixture
O executor do Vitest inicializa suas fixtures de forma inteligente e as injeta no contexto de teste com base no 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 não será executado
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos será executado
test('run', ({ todos }) => {});
WARNING
Ao usar test.extend()
com fixtures, você deve sempre usar o padrão de desestruturação de objeto { todos }
para acessar o contexto tanto na função de fixture quanto na função de teste.
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ática
O Vitest também suporta a sintaxe de tuplas para fixtures, permitindo que você passe opções para cada fixture. Por exemplo, você pode usá-la para inicializar explicitamente uma fixture, mesmo que ela não esteja sendo usada nos testes.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// esta função será executada
setup();
await use();
teardown();
},
{ auto: true }, // Marca como uma fixture automática
],
});
test('works correctly');
Fixture padrão
Desde o Vitest 3, você pode fornecer valores diferentes em diferentes projetos. Para habilitar este recurso, inclua { injected: true }
nas opções. Se a chave não for especificada na configuração do projeto, o valor padrão será usado.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// valor padrão se "url" não for definido na configuração
'/default',
// marca a fixture como "injetada" para permitir a sobrescrita
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url é "/default" em "project-new"
// url é "/full" em "project-full"
// url é "/empty" em "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',
},
},
},
],
},
});
Definindo Escopo de Valores por Suite 3.1.0+
Desde o Vitest 3.1, você pode sobrescrever valores de contexto por suite e seus filhos usando a 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 o novo valor sobrescrito com escopo
// para todos os testes nesta suite
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// suite aninhada herdou o valor com escopo
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// a `dependency` está usando o valor padrão
// fora do escopo definido por `.scoped`
expect(dependant).toEqual({ dependency: 'default' });
});
Esta API é particularmente útil se você tiver um valor de contexto que depende de uma variável dinâmica como uma conexão de banco de dados:
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' });
// ... testes
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... testes
});
Contexto com Escopo Definido 3.2.0+
Você pode definir um contexto que será iniciado uma vez por arquivo ou por worker. Ele é iniciado da mesma forma que uma fixture regular com um parâmetro de objeto:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
O valor é inicializado na primeira vez que qualquer teste o acessa, exceto se as opções da fixture tiverem auto: true
– neste caso, o valor é inicializado antes que qualquer teste seja executado.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// sempre executa este hook antes de qualquer teste
auto: true,
},
],
});
O escopo worker
executará a fixture uma vez por trabalhador. O número de workers ativos depende de vários fatores. Por padrão, cada arquivo é executado em um worker separado, então os escopos file
e worker
funcionam da mesma forma.
No entanto, se você desabilitar o isolamento, o número de workers será limitado pela configuração maxWorkers
ou poolOptions
.
Note que especificar scope: 'worker'
ao executar testes em vmThreads
ou vmForks
funcionará da mesma forma que scope: 'file'
. Essa limitação ocorre porque cada arquivo de teste tem seu próprio contexto de VM. Assim, se o Vitest o iniciasse apenas uma vez, um contexto poderia vazar para outro e criar muitas inconsistências de referência (por exemplo, instâncias da mesma classe apontariam para construtores diferentes).
TypeScript
Para definir tipos de fixtures para todos os seus contextos personalizados, você pode passar o tipo de fixtures como um 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[]>();
});
Inferência de Tipo
Observe que o Vitest não suporta a inferência de tipos quando a função use
é chamada. É sempre preferível passar o tipo de contexto completo como genérico quando test.extend
é chamado:
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
e afterEach
Obsoleto
Esta é uma maneira obsoleta de estender o contexto e não funcionará quando o test
for estendido com test.extend
.
Os contextos são diferentes para cada teste. Você pode acessá-los e estendê-los dentro dos hooks beforeEach
e afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// estende o contexto
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Para fornecer tipos de propriedade para todos os seus contextos personalizados, você pode estender o tipo TestContext
adicionando
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Se você quiser fornecer tipos de propriedade apenas para hooks beforeEach
, afterEach
, it
e test
específicos, você pode passar o tipo como um genérico.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context é 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo é 'string'
console.log(foo); // 'bar'
});