Contexte de test
Inspiré des Playwright Fixtures, le contexte de test de Vitest vous permet de définir des utilitaires, des états internes et des fixtures qui peuvent être utilisés dans vos tests.
Utilisation
Le premier argument de chaque fonction de rappel de test est un contexte de test.
import { it } from 'vitest';
it('should work', ({ task }) => {
// affiche le nom du test en cours
console.log(task.name);
});
Contexte de test intégré
task
Un objet en lecture seule contenant les métadonnées du test.
expect
L'API expect
liée au test actuel :
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Cette API est utile pour exécuter des tests de snapshot en parallèle, car l'objet expect
global ne peut pas les gérer correctement :
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;
Ignore l'exécution restante du test et le marque comme ignoré :
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Depuis Vitest 3.1, cette fonction accepte un paramètre booléen pour ignorer le test conditionnellement :
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>;
Ajoute une annotation de test qui sera affichée par votre rapporteur.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Un AbortSignal
qui peut être annulé par Vitest. Le signal est annulé dans les situations suivantes :
- Le test expire.
- L'utilisateur a annulé manuellement l'exécution du test via Ctrl+C.
vitest.cancelCurrentRun
a été appelé par programme.- Un autre test a échoué en parallèle et le drapeau
bail
est activé.
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
Le hook onTestFailed
lié au test actuel. Cette API est utile si vous exécutez des tests en parallèle et que vous avez besoin d'une gestion spécifique uniquement pour ce test.
onTestFinished
Le hook onTestFinished
lié au test actuel. Cette API est utile si vous exécutez des tests en parallèle et que vous avez besoin d'une gestion spécifique uniquement pour ce test.
Étendre le contexte de test
Vitest propose deux méthodes différentes pour étendre le contexte de test.
test.extend
À l'instar de Playwright, vous pouvez utiliser cette méthode pour définir votre propre API test
avec des fixtures personnalisées et la réutiliser n'importe où.
Par exemple, nous créons d'abord le collecteur test
avec deux fixtures : todos
et archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// initialise la fixture avant chaque test
todos.push(1, 2, 3);
// utilise la valeur de la fixture
await use(todos);
// nettoie la fixture après chaque test
todos.length = 0;
},
archive,
});
Ensuite, nous pouvons l'importer et l'utiliser.
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);
});
Nous pouvons également ajouter d'autres fixtures ou remplacer des fixtures existantes en étendant notre test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Initialisation des fixtures
Le runner Vitest initialisera intelligemment vos fixtures et les injectera dans le contexte de test en fonction de leur utilisation.
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` ne s'exécutera pas
test('skip', () => {});
test('skip', ({ archive }) => {});
// `todos` s'exécutera
test('run', ({ todos }) => {});
WARNING
Lorsque vous utilisez test.extend()
avec des fixtures, vous devez toujours utiliser le modèle de déstructuration d'objet { todos }
pour accéder au contexte, à la fois dans la fonction de fixture et dans la fonction de 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 automatique
Vitest prend également en charge la syntaxe de tuple pour les fixtures, vous permettant de passer des options pour chaque fixture. Par exemple, vous pouvez l'utiliser pour initialiser explicitement une fixture, même si elle n'est pas utilisée dans les tests.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// cette fonction sera exécutée
setup();
await use();
teardown();
},
{ auto: true }, // Marque la fixture comme automatique
],
});
test('works correctly');
Fixture par défaut
Depuis Vitest 3, vous pouvez fournir différentes valeurs dans différents projets. Pour activer cette fonctionnalité, passez { injected: true }
dans les options. Si la clé n'est pas spécifiée dans la configuration du projet, la valeur par défaut sera utilisée.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// valeur par défaut si "url" n'est pas définie dans la configuration
'/default',
// marque la fixture comme "injectée" pour permettre la surcharge
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url est "/default" dans "project-new"
// url est "/full" dans "project-full"
// url est "/empty" dans "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',
},
},
},
],
},
});
Portée des valeurs par suite 3.1.0+
Depuis Vitest 3.1, vous pouvez surcharger les valeurs de contexte par suite et ses enfants en utilisant l'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` utilise la nouvelle valeur surchargée qui a une portée limitée
// à tous les tests de cette suite.
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// la suite imbriquée a hérité de cette valeur
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// la `dependency` utilise la valeur par défaut
// en dehors de la suite avec `.scoped`
expect(dependant).toEqual({ dependency: 'default' });
});
Cette API est particulièrement utile si vous avez une valeur de contexte qui dépend d'une variable dynamique, comme une connexion à une base de données :
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' });
// ... tests
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... tests
});
Contexte par portée 3.2.0+
Vous pouvez définir un contexte qui sera initialisé une fois par fichier ou par worker. Il est initialisé de la même manière qu'une fixture régulière avec un paramètre d'objets :
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
La valeur est initialisée la première fois qu'un test y accède, à moins que les options de la fixture n'incluent auto: true
– dans ce cas, la valeur est initialisée avant l'exécution de tout test.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// exécute toujours ce hook avant tout test
auto: true,
},
],
});
La portée worker
exécutera la fixture une fois par worker. Le nombre de workers en cours d'exécution dépend de divers facteurs. Par défaut, chaque fichier s'exécute dans un worker distinct, de sorte que les portées file
et worker
fonctionnent de la même manière.
Cependant, si vous désactivez l'isolation, le nombre de workers est limité par la configuration maxWorkers
ou poolOptions
.
Notez que spécifier scope: 'worker'
lors de l'exécution de tests dans vmThreads
ou vmForks
fonctionnera de la même manière que scope: 'file'
. Cette limitation existe car chaque fichier de test a son propre contexte VM ; ainsi, si Vitest l'initialisait une seule fois, un contexte pourrait fuir vers un autre et créer de nombreuses incohérences de référence (par exemple, les instances de la même classe feraient référence à des constructeurs différents).
TypeScript
Pour fournir des types de fixture pour tous vos contextes personnalisés, vous pouvez passer le type des fixtures en tant que générique.
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[]>();
});
Inférence de type
Notez que Vitest ne prend pas en charge l'inférence des types lorsque la fonction use
est appelée. Il est toujours préférable de passer le type de contexte entier en tant que type générique lors de l'appel à 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
et afterEach
Déprécié
C'est une méthode obsolète pour étendre le contexte et cela ne fonctionnera pas lorsque test
est étendu avec test.extend
.
Les contextes sont différents pour chaque test. Vous pouvez y accéder et les étendre via les hooks beforeEach
et afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// étend le contexte
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Pour fournir des types de propriété pour tous vos contextes personnalisés, vous pouvez augmenter le type TestContext
en ajoutant :
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Si vous souhaitez fournir des types de propriété uniquement pour des hooks beforeEach
, afterEach
, it
et test
spécifiques, vous pouvez passer le type en tant que générique.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context est 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo est 'string'
console.log(foo); // 'bar'
});