Contesto di Test
Ispirato dalle Playwright Fixtures, il contesto di test di Vitest consente di definire utilità, stati e fixture riutilizzabili all'interno dei test.
Utilizzo
Il primo argomento di ogni callback di test è il contesto di test.
import { it } from 'vitest';
it('dovrebbe funzionare', ({ task }) => {
// Stampa il nome del test.
console.log(task.name);
});
Contesto di Test Predefinito
task
Un oggetto di sola lettura contenente metadati sul test.
expect
L'API expect
associata al test corrente:
import { it } from 'vitest';
it('la matematica è facile', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Questa API è utile per eseguire test snapshot in parallelo, poiché l'expect globale non è in grado di tracciarli correttamente:
import { it } from 'vitest';
it.concurrent('la matematica è facile', ({ expect }) => {
expect(2 + 2).toMatchInlineSnapshot();
});
it.concurrent('la matematica è difficile', ({ expect }) => {
expect(2 * 2).toMatchInlineSnapshot();
});
skip
function skip(note?: string): never;
function skip(condition: boolean, note?: string): void;
Salta l'esecuzione del test successivo e lo contrassegna come ignorato:
import { expect, it } from 'vitest';
it('la matematica è difficile', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
A partire da Vitest 3.1, accetta un parametro booleano per saltare il test condizionalmente:
it('la matematica è difficile', ({ 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>;
Aggiunge un'annotazione di test che verrà visualizzata dal tuo reporter.
test('API delle annotazioni', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Un AbortSignal
che può essere interrotto da Vitest. Il segnale viene interrotto nelle seguenti situazioni:
- Il test va in timeout.
- L'utente ha annullato manualmente l'esecuzione del test con Ctrl+C.
vitest.cancelCurrentRun
è stato invocato programmaticamente.- Un altro test è fallito in parallelo e il flag
bail
è impostato.
it('interrompi la richiesta quando il test va in timeout', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
L'hook onTestFailed
legato al test corrente. Questa API è utile quando si eseguono test in parallelo e si necessita di una gestione speciale solo per questo test specifico.
onTestFinished
L'hook onTestFinished
legato al test corrente. Questa API è utile quando si eseguono test in parallelo e si necessita di una gestione speciale solo per questo test specifico.
Estendere il Contesto di Test
Vitest offre due modi per estendere il contesto di test.
test.extend
Similmente a Playwright, puoi usare questo metodo per definire la tua API test
con fixture personalizzate e riutilizzarla ovunque.
Ad esempio, creiamo prima l'oggetto test
con due fixture: todos
e archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// Imposta la fixture prima di ogni funzione di test.
todos.push(1, 2, 3);
// Utilizza il valore della fixture.
await use(todos);
// Pulisci la fixture dopo ogni funzione di test.
todos.length = 0;
},
archive,
});
Quindi possiamo importarlo e usarlo.
import { expect } from 'vitest';
import { test } from './my-test.js';
test('aggiungi elementi a todos', ({ todos }) => {
expect(todos.length).toBe(3);
todos.push(4);
expect(todos.length).toBe(4);
});
test('sposta elementi da todos ad 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);
});
Possiamo anche aggiungere più fixture o sovrascrivere quelle esistenti estendendo il nostro test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Inizializzazione delle Fixture
Il runner di Vitest inizializzerà automaticamente le tue fixture e le inietterà nel contesto di test in base all'utilizzo.
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" non verrà eseguito
test('salta', () => {});
test('salta', ({ archive }) => {});
// "todos" verrà eseguito
test('esegui', ({ todos }) => {});
WARNING
Quando si usa test.extend()
con le fixture, si dovrebbe sempre usare la destrutturazione dell'oggetto { todos }
per accedere al contesto sia nella funzione fixture che nella funzione test.
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
Fixture Automatica
Vitest supporta anche la sintassi delle tuple per le fixture, permettendoti di passare opzioni per ogni fixture. Ad esempio, puoi usarla per inizializzare esplicitamente una fixture, anche se non viene usata nei test.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// Questa funzione sarà eseguita.
setup();
await use();
teardown();
},
{ auto: true }, // Marca come fixture automatica.
],
});
test('funziona correttamente');
Fixture Predefinita
A partire da Vitest 3, puoi fornire valori diversi per diversi progetti. Per abilitare questa funzionalità, passa { injected: true }
nelle opzioni. Se la chiave non è specificata nella configurazione del progetto, verrà utilizzato il valore predefinito.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// Valore predefinito se "url" non è definito nella configurazione.
'/default',
// Marca la fixture come "injected" per consentire l'override.
{ injected: true },
],
});
test('funziona correttamente', ({ url }) => {
// url è "/default" in "project-new"
// url è "/full" in "project-full"
// url è "/empty" in "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',
},
},
},
],
},
});
Ambito dei Valori per Suite 3.1.0+
Da Vitest 3.1, puoi sovrascrivere i valori del contesto per una suite e i suoi figli usando l'API test.scoped
:
import { test as baseTest, describe, expect } from 'vitest';
const test = baseTest.extend({
dependency: 'default',
dependant: ({ dependency }, use) => use({ dependency }),
});
describe('usa valori con ambito', () => {
test.scoped({ dependency: 'new' });
test('usa valore con ambito', ({ dependant }) => {
// `dependant` usa il nuovo valore sovrascritto che ha un ambito definito
// per tutti i test in questa suite.
expect(dependant).toEqual({ dependency: 'new' });
});
describe('continua a usare il valore con ambito', () => {
test('usa valore con ambito', ({ dependant }) => {
// La suite nidificata ha ereditato il valore.
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('continua a usare i valori predefiniti', ({ dependant }) => {
// La `dependency` sta usando il valore predefinito
// al di fuori della suite con .scoped.
expect(dependant).toEqual({ dependency: 'default' });
});
Questa API è particolarmente utile se hai un valore di contesto che dipende da una variabile dinamica, come una connessione al database:
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('un tipo di schema', () => {
test.scoped({ schema: 'schema-1' });
// ... test
});
describe('un altro tipo di schema', () => {
test.scoped({ schema: 'schema-2' });
// ... test
});
Contesto per Ambito 3.2.0+
Puoi definire un contesto che verrà inizializzato una volta per file o per worker. Viene inizializzato allo stesso modo di una normale fixture con un parametro oggetto:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
Il valore viene inizializzato la prima volta che un test vi accede, a meno che le opzioni della fixture non abbiano auto: true
- in questo caso il valore viene inizializzato prima che qualsiasi test venga eseguito.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// Esegui sempre questo hook prima di qualsiasi test.
auto: true,
},
],
});
Lo scope worker
eseguirà la fixture una volta per worker. Il numero di worker attivi dipende da vari fattori. Per impostazione predefinita, ogni file viene eseguito in un worker separato, quindi gli scope file
e worker
funzionano allo stesso modo.
Tuttavia, se disabiliti l'isolamento, il numero di worker è limitato dalla configurazione maxWorkers
o poolOptions
.
Nota che specificare scope: 'worker'
quando si eseguono test in vmThreads
o vmForks
avrà lo stesso effetto di specificare scope: 'file'
. Questa limitazione esiste perché ogni file di test ha il proprio contesto VM, quindi se Vitest dovesse inizializzarlo una volta, un contesto potrebbe fuoriuscire in un altro e creare molte inconsistenze di riferimento (ad esempio, le istanze della stessa classe farebbero riferimento a costruttori diversi).
TypeScript
Per fornire i tipi di fixture per tutti i tuoi contesti personalizzati, puoi passare il tipo di fixture come generico.
interface MyFixtures {
todos: number[];
archive: number[];
}
const test = baseTest.extend<MyFixtures>({
todos: [],
archive: [],
});
test('i tipi sono definiti correttamente', ({ todos, archive }) => {
expectTypeOf(todos).toEqualTypeOf<number[]>();
expectTypeOf(archive).toEqualTypeOf<number[]>();
});
Inferenza dei Tipi
Nota che Vitest non supporta l'inferenza dei tipi quando la funzione use
viene chiamata. È sempre preferibile passare l'intero tipo di contesto come tipo generico quando test.extend
viene chiamato:
import { test as baseTest } from 'vitest';
const test = baseTest.extend<{
todos: number[];
schema: string;
}>({
todos: ({ schema }, use) => use([]),
schema: 'test',
});
test('i tipi sono corretti', ({
todos, // number[]
schema, // string
}) => {
// ...
});
beforeEach
e afterEach
Deprecato
Questo è un modo obsoleto di estendere il contesto e non funzionerà quando test
viene esteso con test.extend
.
I contesti sono diversi per ogni test. Puoi accedervi ed estenderli all'interno degli hook beforeEach
e afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// Estendi il contesto.
context.foo = 'bar';
});
it('dovrebbe funzionare', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Per fornire i tipi di proprietà per tutti i tuoi contesti personalizzati, puoi estendere il tipo TestContext
aggiungendo:
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Se vuoi fornire i tipi di proprietà solo per specifici hook beforeEach
, afterEach
, it
e test
, puoi passare il tipo come generico.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context è 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('dovrebbe funzionare', ({ foo }) => {
// typeof foo è 'string'
console.log(foo); // 'bar'
});