Testovací kontext
Testovací kontext Vitest, inspirovaný Playwright Fixtures, umožňuje definovat utility, stavy a testovací fixture, které lze použít ve vašich testech.
Použití
Prvním argumentem každé callback funkce testu je kontext testu.
import { it } from 'vitest';
it('should work', ({ task }) => {
// vypíše jméno testu
console.log(task.name);
});
Vestavěný testovací kontext
task
Objekt jen pro čtení obsahující metadata o testu.
expect
API expect
, které je vázáno na aktuální test:
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Toto API je užitečné pro souběžné provádění snapshot testů, protože globální expect
je nedokáže sledovat:
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;
Přeskočí následné provádění testu a označí test jako přeskočený:
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Od verze Vitest 3.1 přijímá booleovský parametr pro podmíněné přeskočení testu:
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>;
Přidá anotaci k testu, která bude zobrazena vaším reportérem.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
AbortSignal
, který může být zrušen Vitestem. Signál je zrušen v následujících situacích:
- Test vyprší
- Uživatel ručně zrušil spuštění testu pomocí Ctrl+C
vitest.cancelCurrentRun
bylo voláno programově- Jiný test selhal souběžně a je nastaven příznak
bail
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
Hook onTestFailed
vázaný na aktuální test. Toto API je užitečné, pokud provádíte testy souběžně a potřebujete speciální zpracování pouze pro tento konkrétní test.
onTestFinished
Hook onTestFinished
vázaný na aktuální test. Toto API je užitečné, pokud provádíte testy souběžně a potřebujete speciální zpracování pouze pro tento konkrétní test.
Rozšíření testovacího kontextu
Vitest poskytuje dva různé způsoby, jak vám pomoci rozšířit testovací kontext.
test.extend
Podobně jako Playwright, můžete tuto metodu použít k definování vlastního test
API s vlastními fixtures a jeho opětovnému použití kdekoli.
Například nejprve vytvoříme instanci test
se dvěma fixtures: todos
a archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// nastavit fixture před každou testovací funkcí
todos.push(1, 2, 3);
// použít hodnotu fixture
await use(todos);
// vyčistit fixture po každé testovací funkci
todos.length = 0;
},
archive,
});
Pak jej můžeme importovat a použít.
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);
});
Můžeme také přidat další fixtures nebo přepsat stávající fixtures rozšířením našeho test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Inicializace fixture
Vitest runner chytře inicializuje vaše fixtures a vloží je do kontextu testu na základě jejich použití.
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 se nespustí
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos se spustí
test('run', ({ todos }) => {});
WARNING
Při použití test.extend()
s fixtures byste měli vždy používat vzorec pro destrukturalizaci objektu { todos }
pro přístup ke kontextu jak ve funkci fixture, tak ve funkci testu.
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
Automatická fixture
Vitest také podporuje syntaxi n-tice pro fixtures, což vám umožňuje předávat možnosti pro každou fixture. Například ji můžete použít k explicitní inicializaci fixture, i když se v testech nepoužívá.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// tato funkce bude spuštěna
setup();
await use();
teardown();
},
{ auto: true }, // Označit jako automatickou fixture
],
});
test('works correctly');
Výchozí fixture
Od Vitest 3 můžete poskytovat různé hodnoty v různých projektech. Pro povolení této funkce předejte { injected: true }
do možností. Pokud klíč není specifikován v konfiguraci projektu, pak bude použita výchozí hodnota.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// výchozí hodnota, pokud "url" není definováno v konfiguraci
'/default',
// označit fixture jako "injected" pro povolení přepsání
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url je "/default" v "project-new"
// url je "/full" v "project-full"
// url je "/empty" v "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',
},
},
},
],
},
});
Rozsah hodnot pro testovací sadu 3.1.0+
Od Vitest 3.1 můžete přepsat hodnoty kontextu pro každou sadu a její potomky pomocí 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` používá novou přepsanou hodnotu, která je v rozsahu
// všech testů v této sadě
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// vnořená testovací sada zdědila hodnotu
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// `dependency` používá výchozí
// hodnotu mimo sadu s .scoped
expect(dependant).toEqual({ dependency: 'default' });
});
Toto API je obzvláště užitečné, pokud máte hodnotu kontextu, která závisí na dynamické proměnné, jako je připojení k databázi:
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' });
// ... testy
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... testy
});
Kontext podle rozsahu 3.2.0+
Můžete definovat kontext, který bude inicializován jednou pro soubor nebo worker. Inicializuje se stejným způsobem jako běžná fixture s parametrem objektů:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
Hodnota je inicializována poprvé, když k ní přistoupí jakýkoli test, pokud možnosti fixture nemají auto: true
– v tomto případě je hodnota inicializována před spuštěním jakéhokoli testu.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// vždy spustit tento hook před jakýmkoli testem
auto: true,
},
],
});
Rozsah worker
spustí fixture jednou pro každého workera. Počet spuštěných workerů závisí na různých faktorech. Ve výchozím nastavení se každý soubor spouští v samostatném workeru, takže rozsahy file
a worker
fungují stejně.
Pokud však zakážete izolaci, pak je počet workerů omezen konfigurací maxWorkers
nebo poolOptions
.
Všimněte si, že specifikace scope: 'worker'
při spouštění testů v vmThreads
nebo vmForks
bude fungovat stejně jako scope: 'file'
. Toto omezení existuje, protože každý testovací soubor má svůj vlastní VM kontext, takže pokud by ho Vitest inicializoval jednou, jeden kontext by mohl uniknout do druhého a vytvořit mnoho nekonzistencí referencí (například instance stejné třídy by odkazovaly na různé konstruktory).
TypeScript
Pro poskytnutí typů fixtures pro všechny vaše vlastní kontexty můžete předat typ fixture jako generický.
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[]>();
});
Odvozování typů
Všimněte si, že Vitest nepodporuje odvozování typů, když je volána funkce use
. Vždy je lepší předat celý typ kontextu jako generický typ, když je volán 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
a afterEach
Zastaralé
Toto je zastaralý způsob rozšíření kontextu a nebude fungovat, pokud je test
rozšířen pomocí test.extend
.
Kontexty jsou pro každý test odlišné. Můžete k nim přistupovat a rozšiřovat je v rámci hooků beforeEach
a afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// rozšířit kontext
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Pro poskytnutí typů vlastností pro všechny vaše vlastní kontexty můžete rozšířit typ TestContext
přidáním
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Pokud chcete poskytnout typy vlastností pouze pro konkrétní hooky beforeEach
, afterEach
, it
a test
, můžete předat typ jako generický.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context je 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo je 'string'
console.log(foo); // 'bar'
});