Контекст теста
Вдохновленный Playwright Fixtures, контекст теста Vitest позволяет определять утилиты, состояния и фикстуры, используемые в ваших тестах.
Использование
Первый аргумент каждого коллбэка теста — это контекст теста.
import { it } from 'vitest';
it('should work', ({ task }) => {
// выводит название теста
console.log(task.name);
});
Встроенный контекст теста
task
Объект, доступный только для чтения, содержащий метаданные о тесте.
expect
API expect
, связанный с текущим тестом:
import { it } from 'vitest';
it('math is easy', ({ expect }) => {
expect(2 + 2).toBe(4);
});
Этот API полезен при параллельном выполнении снапшот-тестов, поскольку глобальный expect
не может отслеживать их состояние:
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;
Пропускает дальнейшее выполнение теста и помечает тест как пропущенный:
import { expect, it } from 'vitest';
it('math is hard', ({ skip }) => {
skip();
expect(2 + 2).toBe(5);
});
Начиная с Vitest 3.1, он принимает логический параметр для условного пропуска теста:
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>;
Добавляет аннотацию для теста, которая будет отображаться в вашем репортере.
test('annotations API', async ({ annotate }) => {
await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues');
});
signal
3.2.0+
AbortSignal
, который может быть прерван Vitest. Сигнал будет прерван в следующих случаях:
- Время выполнения теста истекло.
- Пользователь вручную отменил запуск теста с помощью Ctrl+C.
vitest.cancelCurrentRun
был вызван программно.- Другой тест параллельно завершился с ошибкой, и установлен флаг
bail
.
it('stop request when test times out', async ({ signal }) => {
await fetch('/resource', { signal });
}, 2000);
onTestFailed
Хук onTestFailed
, связанный с текущим тестом. Этот API полезен, если вы запускаете тесты параллельно и требуется специальная обработка только для этого конкретного теста.
onTestFinished
Хук onTestFinished
, связанный с текущим тестом. Этот API полезен, если вы запускаете тесты параллельно и требуется специальная обработка только для этого конкретного теста.
Расширение контекста теста
Vitest предоставляет два способа расширения контекста теста.
test.extend
Как и Playwright, вы можете использовать этот метод для создания собственного API test
с пользовательскими фикстурами и их повторного использования в любом месте.
Например, сначала мы создаем объект test
с двумя фикстурами: todos
и archive
.
import { test as baseTest } from 'vitest';
const todos = [];
const archive = [];
export const test = baseTest.extend({
todos: async ({}, use) => {
// настройка фикстуры перед каждым тестом
todos.push(1, 2, 3);
// использование значения фикстуры
await use(todos);
// очистка фикстуры после каждого теста
todos.length = 0;
},
archive,
});
Затем мы можем импортировать и использовать его.
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);
});
Мы также можем добавить больше фикстур или переопределить существующие фикстуры, расширив наш test
.
import { test as todosTest } from './my-test.js';
export const test = todosTest.extend({
settings: {
// ...
},
});
Инициализация фикстуры
Раннер Vitest будет автоматически инициализировать ваши фикстуры и внедрять их в контекст теста в зависимости от их использования.
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 не будет инициализирован
test('skip', () => {});
test('skip', ({ archive }) => {});
// todos будет инициализирован
test('run', ({ todos }) => {});
WARNING
При использовании test.extend()
с фикстурами, вы всегда должны использовать деструктуризацию объекта { todos }
для доступа к контексту как в функции фикстуры, так и в функции теста.
test('context must be destructured', (context) => {
expect(context.todos.length).toBe(2)
})
test('context must be destructured', ({ todos }) => {
expect(todos.length).toBe(2)
})
Автоматическая фикстура
Vitest также поддерживает кортежный синтаксис для фикстур, позволяя передавать опции для каждой фикстуры. Например, вы можете использовать его для явной инициализации фикстуры, даже если она не используется в тестах.
import { test as base } from 'vitest';
const test = base.extend({
fixture: [
async ({}, use) => {
// эта функция будет запущена
setup();
await use();
teardown();
},
{ auto: true }, // Указать, что фикстура автоматическая
],
});
test('works correctly');
Фикстура по умолчанию
Начиная с Vitest 3, вы можете задавать разные значения для различных проектов. Чтобы включить эту функцию, передайте { injected: true }
в опции. Если ключ не задан в конфигурации проекта, используется значение по умолчанию.
import { test as base } from 'vitest';
const test = base.extend({
url: [
// значение по умолчанию, если "url" не определен в конфигурации
'/default',
// пометить фикстуру как "injected", чтобы разрешить переопределение
{ injected: true },
],
});
test('works correctly', ({ url }) => {
// url равен "/default" в "project-new"
// url равен "/full" в "project-full"
// url равен "/empty" в "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',
},
},
},
],
},
});
Область видимости значений в наборе тестов 3.1.0+
Начиная с Vitest 3.1, вы можете переопределять значения контекста для каждого набора тестов и его дочерних элементов, используя 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` использует новое переопределенное значение, которое действует
// для всех тестов в этом наборе
expect(dependant).toEqual({ dependency: 'new' });
});
describe('keeps using scoped value', () => {
test('uses scoped value', ({ dependant }) => {
// вложенный набор тестов унаследовал значение
expect(dependant).toEqual({ dependency: 'new' });
});
});
});
test('keep using the default values', ({ dependant }) => {
// `dependency` использует значение по умолчанию
// вне набора с .scoped
expect(dependant).toEqual({ dependency: 'default' });
});
Этот API особенно полезен, если у вас есть значение контекста, которое зависит от динамической переменной, такой как подключение к базе данных:
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' });
// ... тесты
});
describe('another type of schema', () => {
test.scoped({ schema: 'schema-2' });
// ... тесты
});
Контекст с заданной областью видимости 3.2.0+
Вы можете определить контекст, который инициализируется один раз для каждого файла или воркера. Он инициализируется так же, как обычная фикстура, с использованием объекта параметров:
import { test as baseTest } from 'vitest';
export const test = baseTest.extend({
perFile: [({}, { use }) => use([]), { scope: 'file' }],
perWorker: [({}, { use }) => use([]), { scope: 'worker' }],
});
Значение инициализируется при первом доступе к нему любым тестом, если только опции фикстуры не содержат auto: true
. В этом случае значение инициализируется до запуска любого теста.
const test = baseTest.extend({
perFile: [
({}, { use }) => use([]),
{
scope: 'file',
// всегда запускать этот хук перед любым тестом
auto: true,
},
],
});
Область видимости worker
инициализирует фикстуру один раз для каждого воркера. Количество запущенных воркеров зависит от многих факторов. По умолчанию каждый файл запускается в отдельном воркере, поэтому области видимости file
и worker
функционируют одинаково.
Однако, если вы отключите изоляцию, количество воркеров будет ограничено конфигурацией maxWorkers
или poolOptions
.
Обратите внимание, что указание scope: 'worker'
при запуске тестов в vmThreads
или vmForks
будет работать так же, как scope: 'file'
. Это ограничение связано с тем, что каждый тестовый файл имеет свой собственный контекст VM. Если Vitest инициализирует его один раз, один контекст может повлиять на другой, что приведет к многочисленным несоответствиям ссылок (например, экземпляры одного и того же класса будут ссылаться на разные конструкторы).
TypeScript
Чтобы предоставить типы фикстур для всех ваших пользовательских контекстов, вы можете передать тип фикстур как дженерик.
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[]>();
});
Выведение типов
Обратите внимание, что Vitest не поддерживает выведение типов при вызове функции use
. Рекомендуется всегда передавать полный тип контекста как дженерик-тип при вызове 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
и afterEach
Устарело
Это устаревший метод расширения контекста, и он не будет работать, если test
был расширен с помощью test.extend
.
Контексты отличаются для каждого теста. Вы можете получить к ним доступ и расширить их в хуках beforeEach
и afterEach
.
import { beforeEach, it } from 'vitest';
beforeEach(async context => {
// расширить контекст
context.foo = 'bar';
});
it('should work', ({ foo }) => {
console.log(foo); // 'bar'
});
TypeScript
Чтобы предоставить типы свойств для всех ваших пользовательских контекстов, вы можете расширить тип TestContext
, добавив следующее:
declare module 'vitest' {
export interface TestContext {
foo?: string;
}
}
Если вы хотите предоставить типы свойств только для определенных хуков beforeEach
, afterEach
, it
и test
, вы можете передать тип как дженерик.
interface LocalTestContext {
foo: string;
}
beforeEach<LocalTestContext>(async context => {
// typeof context is 'TestContext & LocalTestContext'
context.foo = 'bar';
});
it<LocalTestContext>('should work', ({ foo }) => {
// typeof foo is 'string'
console.log(foo); // 'bar'
});