Test-Kontext
Inspiriert von Playwright Fixtures ermöglicht der Test-Kontext von Vitest die Definition von Hilfsfunktionen, Zuständen und Fixtures, die in Ihren Tests verwendet werden können.
Verwendung
Das erste Argument jedes Test-Callbacks ist ein Test-Kontext.
import { it } from 'vitest';
it('should work', ({ task }) => {
// Gibt den Namen des Tests aus
console.log(task.name);
});
Eingebauter Test-Kontext
task
Ein schreibgeschütztes Objekt, das Metadaten über den Test enthält.
expect
Die expect
-API ist an den aktuellen Test gebunden:
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Diese API ist nützlich für die parallele Ausführung von Snapshot-Tests, da das globale expect
sie nicht verfolgen kann:
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;
Überspringt die Ausführung nachfolgender Testschritte und markiert den Test als übersprungen:
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Seit Vitest 3.1 akzeptiert die Funktion einen booleschen Parameter, um den Test bedingt zu überspringen:
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>;
Fügt eine Test-Annotation hinzu, die in Ihrem Reporter angezeigt wird.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
Ein AbortSignal
, das von Vitest abgebrochen werden kann. Das Signal wird in folgenden Situationen abgebrochen:
- Der Test überschreitet die Zeitvorgabe.
- Der Benutzer hat den Testlauf manuell mit Strg+C abgebrochen.
vitest.cancelCurrentRun
wurde programmgesteuert aufgerufen.- Ein anderer Test ist parallel fehlgeschlagen, und das
bail
-Flag ist gesetzt.
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
Der onTestFailed
-Hook ist an den aktuellen Test gebunden. Diese API ist nützlich, wenn Sie Tests gleichzeitig ausführen und eine spezielle Behandlung nur für diesen spezifischen Test benötigen.
onTestFinished
Der onTestFinished
-Hook ist an den aktuellen Test gebunden. Diese API ist nützlich, wenn Sie Tests gleichzeitig ausführen und eine spezielle Behandlung nur für diesen spezifischen Test benötigen.
Test-Kontext erweitern
Vitest bietet zwei verschiedene Methoden zur Erweiterung des Test-Kontexts an.
test.extend
Ähnlich wie bei Playwright können Sie diese Methode nutzen, um Ihre eigene test
-API mit benutzerdefinierten Fixtures zu definieren und überall wiederzuverwenden.
Zum Beispiel erstellen wir zuerst den test
-Collector mit zwei Fixtures: todos
und archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// Fixture vor jeder Testfunktion vorbereiten
todos.push(1, 2, 3);
// Fixture-Wert verwenden
await use(todos);
// Fixture nach jeder Testfunktion aufräumen
todos.length = 0;
},
archive,
});
Anschließend können wir es importieren und verwenden.
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);
});
Zusätzlich können wir weitere Fixtures hinzufügen oder bestehende Fixtures überschreiben, indem wir unseren test
erweitern.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Fixture-Initialisierung
Der Vitest-Runner initialisiert Ihre Fixtures bedarfsgerecht und injiziert sie basierend auf deren Verwendung in den Test-Kontext.
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 wird nicht ausgeführt
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos wird ausgeführt
test('run', ({ todos }) => {});
WARNING
Wenn Sie test.extend()
mit Fixtures verwenden, sollten Sie immer das Objekt-Destrukturierungs-Muster { todos }
nutzen, um sowohl in der Fixture-Funktion als auch in der Testfunktion auf den Kontext zuzugreifen.
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
Automatische Fixture
Vitest unterstützt auch die Tupel-Syntax für Fixtures, die es Ihnen ermöglicht, Optionen für jede Fixture zu übergeben. Zum Beispiel können Sie damit eine Fixture explizit initialisieren, auch wenn sie in Tests nicht verwendet wird.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// dieser Hook wird ausgeführt
setup();
await use();
teardown();
},
{ auto: true }, // Als automatische Fixture markieren
],
});
test('works correctly');
Standard-Fixture
Seit Vitest 3 können Sie verschiedene Werte in verschiedenen Projekten bereitstellen. Um diese Funktion zu aktivieren, übergeben Sie { injected: true }
an die Optionen. Wenn der Schlüssel in der Projektkonfiguration nicht angegeben ist, wird der Standardwert verwendet.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// Standardwert, wenn "url" in der Konfiguration nicht definiert ist
'/default',
// Fixture als "injected" markieren, um das Überschreiben zu ermöglichen
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url ist "/default" in "project-new"
// url ist "/full" in "project-full"
// url ist "/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',
},
},
},
],
},
});
Werte auf Suite beschränken 3.1.0+
Seit Vitest 3.1 können Sie Kontextwerte pro Suite und deren Kindern überschreiben, indem Sie die test.scoped
-API verwenden:
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` verwendet den neuen überschriebenen Wert, der auf alle Tests in dieser Suite beschränkt ist
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// Geschachtelte Suite hat den Wert geerbt
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// Die `dependency` verwendet den Standardwert außerhalb der Suite, die mit `.scoped` definiert wurde
expect(dependant).toEqual({ dependency: 'default' });
});
Diese API ist besonders nützlich, wenn Sie einen Kontextwert haben, der von einer dynamischen Variable wie einer Datenbankverbindung abhängt:
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
});
Kontext pro Geltungsbereich 3.2.0+
Sie können einen Kontext definieren, der einmal pro Datei oder Worker initialisiert wird. Er wird auf die gleiche Weise wie eine reguläre Fixture mit einem Objekt als Parameter initialisiert:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
Der Wert wird initialisiert, sobald ein Test darauf zugreift. Eine Ausnahme bildet der Fall, wenn die Fixture-Optionen auto: true
enthalten – dann wird der Wert initialisiert, bevor ein Test ausgeführt wird.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// diesen Hook immer vor jedem Test ausführen
auto: true,
},
],
});
Der worker
-Geltungsbereich führt die Fixture einmal pro Worker aus. Die Anzahl der laufenden Worker hängt von verschiedenen Faktoren ab. Standardmäßig läuft jede Datei in einem separaten Worker, sodass die Geltungsbereiche file
und worker
auf die gleiche Weise funktionieren.
Wenn Sie jedoch die Isolation deaktivieren, wird die Anzahl der Worker durch die Konfiguration von maxWorkers
oder poolOptions
begrenzt.
Beachten Sie, dass die Angabe von scope: 'worker'
beim Ausführen von Tests in vmThreads
oder vmForks
auf die gleiche Weise funktioniert wie scope: 'file'
. Diese Einschränkung besteht, weil jede Testdatei ihren eigenen VM-Kontext hat. Wenn Vitest sie einmal initialisieren würde, könnte ein Kontext in einen anderen durchsickern und viele Referenzinkonsistenzen verursachen (Instanzen derselben Klasse würden beispielsweise auf verschiedene Konstruktoren verweisen).
TypeScript
Um Fixture-Typen für alle Ihre benutzerdefinierten Kontexte bereitzustellen, können Sie den Fixture-Typ als generischen Typ übergeben.
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[]>();
});
Typinferenz
Beachten Sie, dass Vitest die Typen nicht ableiten kann, wenn die use
-Funktion aufgerufen wird. Es ist immer empfehlenswert, den gesamten Kontexttyp als generischen Typ zu übergeben, wenn test.extend
aufgerufen wird:
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
und afterEach
Veraltet
Dies ist eine veraltete Methode zur Kontext-Erweiterung und funktioniert nicht, wenn der test
mit test.extend
erweitert wird.
Die Kontexte sind für jeden Test unterschiedlich. Sie können sie innerhalb der beforeEach
- und afterEach
-Hooks aufrufen und erweitern.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// Kontext erweitern
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Um Eigenschaftstypen für alle Ihre benutzerdefinierten Kontexte bereitzustellen, können Sie den Typ TestContext
erweitern, indem Sie Folgendes hinzufügen:
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Wenn Sie Eigenschaftstypen nur für bestimmte beforeEach
-, afterEach
-, it
- und test
-Hooks bereitstellen möchten, können Sie den Typ als generischen Typ übergeben.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context ist 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo ist 'string'
console.log(foo); // 'bar'
});