Runner API
WARNING
Этот API является расширенным. Если вы просто хотите запустить тесты, он, вероятно, вам не понадобится. Он в основном используется авторами библиотек.
Вы можете указать путь к вашему тестовому раннеру с помощью опции runner
в файле конфигурации. Этот файл должен иметь экспорт по умолчанию с конструктором класса, реализующим следующие методы:
export interface VitestRunner {
/**
* Вызывается в первую очередь, перед сбором и запуском тестов.
*/
onBeforeCollect?: (paths: string[]) => unknown;
/**
* Вызывается после сбора тестов и перед "onBeforeRun".
*/
onCollected?: (files: File[]) => unknown;
/**
* Вызывается, когда тестовый раннер должен отменить последующие запуски тестов.
* Раннер должен отслеживать этот метод и помечать тесты и наборы как пропущенные в
* "onBeforeRunSuite" и "onBeforeRunTask".
*/
onCancel?: (reason: CancelReason) => unknown;
/**
* Вызывается перед запуском отдельного теста. Поле "result" пока отсутствует.
*/
onBeforeRunTask?: (test: TaskPopulated) => unknown;
/**
* Вызывается непосредственно перед запуском тестовой функции. Поле "result" уже содержит "state" и "startTime".
*/
onBeforeTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Вызывается после установки результата и состояния.
*/
onAfterRunTask?: (test: TaskPopulated) => unknown;
/**
* Вызывается сразу после запуска тестовой функции. Новое состояние еще не установлено. Не будет вызван, если тестовая функция выбросит ошибку.
*/
onAfterTryTask?: (
test: TaskPopulated,
options: { retry: number; repeats: number }
) => unknown;
/**
* Вызывается перед запуском отдельного набора. Поле "result" пока отсутствует.
*/
onBeforeRunSuite?: (suite: Suite) => unknown;
/**
* Вызывается после запуска одного набора. Имеет состояние и результат.
*/
onAfterRunSuite?: (suite: Suite) => unknown;
/**
* Если определено, будет вызвано вместо стандартного разделения и обработки наборов Vitest.
* Хуки "before" и "after" будут учитываться.
*/
runSuite?: (suite: Suite) => Promise<void>;
/**
* Если определено, будет вызвано вместо стандартной обработки тестов Vitest. Полезно, если у вас есть собственная тестовая функция.
* Хуки "before" и "after" будут учитываться.
*/
runTask?: (test: TaskPopulated) => Promise<void>;
/**
* Вызывается при обновлении задачи. Аналогично "onTaskUpdate" в репортере, но выполняется в том же потоке, что и тесты.
*/
onTaskUpdate?: (
task: [string, TaskResult | undefined, TaskMeta | undefined][]
) => Promise<void>;
/**
* Вызывается перед запуском всех тестов в собранных путях.
*/
onBeforeRunFiles?: (files: File[]) => unknown;
/**
* Вызывается сразу после запуска всех тестов в собранных путях.
*/
onAfterRunFiles?: (files: File[]) => unknown;
/**
* Вызывается, когда определяется новый контекст для теста. Полезно, если вы хотите добавить пользовательские свойства в контекст.
* Если вы хотите определить пользовательский контекст только с использованием раннера, рассмотрите возможность использования "beforeAll" в "setupFiles" вместо этого.
*/
extendTaskContext?: (context: TestContext) => TestContext;
/**
* Вызывается при импорте определенных файлов. Может быть использован в двух случаях: для сбора тестов и для импорта файлов настройки.
*/
importFile: (filepath: string, source: VitestRunnerImportSource) => unknown;
/**
* Функция, вызываемая раннером при попытке получить значение, когда `test.extend` используется с `{ injected: true }`
*/
injectValue?: (key: string) => unknown;
/**
* Общедоступная конфигурация.
*/
config: VitestRunnerConfig;
/**
* Имя текущего пула. Может влиять на то, как трассировка стека выводится на стороне сервера.
*/
pool?: string;
}
При инициализации этого класса Vitest передает конфигурацию Vitest — вы должны сделать ее доступной через свойство config
:
import type { RunnerTestFile } from 'vitest';
import type { VitestRunner, VitestRunnerConfig } from 'vitest/suite';
import { VitestTestRunner } from 'vitest/runners';
class CustomRunner extends VitestTestRunner implements VitestRunner {
public config: VitestRunnerConfig;
constructor(config: VitestRunnerConfig) {
this.config = config;
}
onAfterRunFiles(files: RunnerTestFile[]) {
console.log('finished running', files);
}
}
export default CustomRunner;
WARNING
Vitest также предоставляет экземпляр ViteNodeRunner
как свойство __vitest_executor
. Вы можете использовать его для обработки файлов в методе importFile
(это стандартное поведение для TestRunner
и BenchmarkRunner
).
ViteNodeRunner
предоставляет метод executeId
, который используется для импорта тестовых файлов в среде, дружественной Vite. Это означает, что он будет разрешать импорты и преобразовывать содержимое файла во время выполнения для совместимости с Node.js:
export default class Runner {
async importFile(filepath: string) {
await this.__vitest_executor.executeId(filepath);
}
}
WARNING
Если у вас отсутствует пользовательский раннер или не определен метод runTask
, Vitest попытается получить задачу автоматически. Если вы не добавили функцию через setFn
, это вызовет ошибку.
TIP
Поддержка снимков и некоторые другие функции зависят от раннера. Чтобы сохранить их, вы можете расширить свой раннер из VitestTestRunner
, импортированного из vitest/runners
. Он также предоставляет BenchmarkNodeRunner
, если вы хотите расширить функциональность бенчмарков.
Задачи
WARNING
"Runner Tasks API" является экспериментальным и должен использоваться в основном только во время выполнения тестов. Vitest также предоставляет "Reported Tasks API", который следует предпочесть при работе в основном потоке (например, внутри репортера).
Команда в настоящее время обсуждает, следует ли в будущем заменить "Runner Tasks" на "Reported Tasks".
Наборы и тесты внутренне называются tasks
. Раннер Vitest инициирует задачу File
перед сбором любых тестов — это надмножество Suite
с несколькими дополнительными свойствами. Оно доступно для каждой задачи (включая File
) через свойство file
.
interface File extends Suite {
/**
* Имя пула, к которому принадлежит файл.
* @default 'forks'
*/
pool?: string;
/**
* Путь к файлу в формате UNIX.
*/
filepath: string;
/**
* Имя тестового проекта, к которому принадлежит файл.
*/
projectName: string | undefined;
/**
* Время, затраченное на сбор всех тестов в файле.
* Это время также включает импорт всех зависимостей файла.
*/
collectDuration?: number;
/**
* Время, затраченное на импорт файла настройки.
*/
setupDuration?: number;
}
Каждый набор имеет свойство tasks
, которое заполняется на этапе сбора. Это свойство полезно для навигации по дереву задач сверху вниз.
interface Suite extends TaskBase {
type: 'suite';
/**
* Задача файла. Это корневая задача файла.
*/
file: File;
/**
* Массив задач, которые являются частью набора.
*/
tasks: Task[];
}
Каждая задача имеет свойство suite
, которое ссылается на родительский набор. Если test
или describe
определяются на верхнем уровне, у них не будет свойства suite
(оно не будет равно file
!). File
также никогда не имеет свойства suite
. Это свойство полезно для навигации по задачам снизу вверх.
interface Test<ExtraContext = object> extends TaskBase {
type: 'test';
/**
* Контекст теста, который будет передан в тестовую функцию.
*/
context: TestContext & ExtraContext;
/**
* Задача файла. Это корневая задача файла.
*/
file: File;
/**
* Была ли задача пропущена вызовом `context.skip()`.
*/
pending?: boolean;
/**
* Следует ли считать задачу успешной, даже если она завершится неудачей. Если задача завершится неудачей, она будет помечена как пройденная.
*/
fails?: boolean;
/**
* Сохранять промисы (из асинхронных ожиданий), чтобы дождаться их перед завершением теста
*/
promises?: Promise<any>[];
}
Каждая задача может иметь поле result
. Наборы могут иметь это поле только в том случае, если ошибка, возникшая в колбэке набора или колбэках beforeAll
/afterAll
, препятствует сбору тестов. Тесты всегда имеют это поле после вызова их колбэков — поля state
и errors
заполняются в зависимости от исхода выполнения. Если ошибка была выброшена в колбэках beforeEach
или afterEach
, ошибка, которая была выброшена, будет присутствовать в task.result.errors
.
export interface TaskResult {
/**
* Состояние задачи. Принимает значение `task.mode` во время сбора.
* Когда задача завершена, она изменится на `pass` или `fail`.
* - **pass**: тест пройден успешно
* - **fail**: тест не пройден
*/
state: TaskState;
/**
* Ошибки, возникшие во время выполнения задачи. Возможно наличие нескольких ошибок,
* если `expect.soft()` провалился несколько раз.
*/
errors?: ErrorWithDiff[];
/**
* Длительность выполнения задачи в миллисекундах.
*/
duration?: number;
/**
* Время начала выполнения задачи в миллисекундах.
*/
startTime?: number;
/**
* Размер кучи в байтах после выполнения задачи.
* Доступно только если установлена опция `logHeapUsage` и определен `process.memoryUsage`.
*/
heap?: number;
/**
* Состояние хуков, связанных с этой задачей. Полезно при формировании отчетов.
*/
hooks?: Partial<
Record<'afterAll' | 'beforeAll' | 'beforeEach' | 'afterEach', TaskState>
>;
/**
* Количество раз, когда задача была перезапущена. Задача перезапускается только если она
* провалилась и установлена опция `retry`.
*/
retryCount?: number;
/**
* Количество раз, когда задача была повторно выполнена. Задача повторно выполняется только если
* установлена опция `repeats`. Это число включает `retryCount`.
*/
repeatCount?: number;
}
Ваша функция задачи
Vitest предоставляет утилиту createTaskCollector
для создания собственного метода test
. Он функционирует как обычный тест, но вызывает пользовательский метод во время сбора.
Задача — это объект, который является частью набора. Он автоматически добавляется в текущий набор через метод suite.task
:
import { createTaskCollector, getCurrentSuite } from 'vitest/suite';
export { afterAll, beforeAll, describe } from 'vitest';
// эта функция вызывается на этапе сбора:
// не вызывайте здесь функцию-обработчик, добавьте ее в задачи набора
// с помощью метода "getCurrentSuite().task()"
// примечание: createTaskCollector обеспечивает возможность поддержки "todo"/"each"/...
export const myCustomTask = createTaskCollector(function (name, fn, timeout) {
getCurrentSuite().task(name, {
...this, // для корректного отслеживания "todo"/"skip"/...
meta: {
customPropertyToDifferentiateTask: true,
},
handler: fn,
timeout,
});
});
import { afterAll, beforeAll, describe, myCustomTask } from './custom.js';
import { gardener } from './gardener.js';
describe('take care of the garden', () => {
beforeAll(() => {
gardener.putWorkingClothes();
});
myCustomTask('weed the grass', () => {
gardener.weedTheGrass();
});
myCustomTask.todo('mow the lawn', () => {
gardener.mowerTheLawn();
});
myCustomTask('water flowers', () => {
gardener.waterFlowers();
});
afterAll(() => {
gardener.goHome();
});
});
vitest ./garden/tasks.test.js