# Test Runner ::: warning This is advanced API. If you just want to [run tests](/guide/), you probably don't need this. It is primarily used by library authors. ::: You can specify a path to your test runner with the `runner` option in your configuration file. This file should have a default export with a class constructor implementing these methods: ```ts export interface VitestRunner { /** * First thing that's getting called before actually collecting and running tests. */ onBeforeCollect?: (paths: string[]) => unknown /** * Called after collecting tests and before "onBeforeRun". */ onCollected?: (files: File[]) => unknown /** * Called when test runner should cancel next test runs. * Runner should listen for this method and mark tests and suites as skipped in * "onBeforeRunSuite" and "onBeforeRunTask" when called. */ onCancel?: (reason: CancelReason) => unknown /** * Called before running a single test. Doesn't have "result" yet. */ onBeforeRunTask?: (test: TaskPopulated) => unknown /** * Called before actually running the test function. Already has "result" with "state" and "startTime". */ onBeforeTryTask?: (test: TaskPopulated, options: { retry: number; repeats: number }) => unknown /** * Called after result and state are set. */ onAfterRunTask?: (test: TaskPopulated) => unknown /** * Called right after running the test function. Doesn't have new state yet. Will not be called, if the test function throws. */ onAfterTryTask?: (test: TaskPopulated, options: { retry: number; repeats: number }) => unknown /** * Called before running a single suite. Doesn't have "result" yet. */ onBeforeRunSuite?: (suite: Suite) => unknown /** * Called after running a single suite. Has state and result. */ onAfterRunSuite?: (suite: Suite) => unknown /** * If defined, will be called instead of usual Vitest suite partition and handling. * "before" and "after" hooks will not be ignored. */ runSuite?: (suite: Suite) => Promise /** * If defined, will be called instead of usual Vitest handling. Useful, if you have your custom test function. * "before" and "after" hooks will not be ignored. */ runTask?: (test: TaskPopulated) => Promise /** * Called, when a task is updated. The same as "onTaskUpdate" in a reporter, but this is running in the same thread as tests. */ onTaskUpdate?: (task: [string, TaskResult | undefined][]) => Promise /** * Called before running all tests in collected paths. */ onBeforeRunFiles?: (files: File[]) => unknown /** * Called right after running all tests in collected paths. */ onAfterRunFiles?: (files: File[]) => unknown /** * Called when new context for a test is defined. Useful, if you want to add custom properties to the context. * If you only want to define custom context with a runner, consider using "beforeAll" in "setupFiles" instead. * * This method is called for both "test" and "custom" handlers. * * @see https://vitest.dev/advanced/runner.html#your-task-function */ extendTaskContext?: (context: TaskContext) => TaskContext /** * Called, when certain files are imported. Can be called in two situations: when collecting tests and when importing setup files. */ importFile: (filepath: string, source: VitestRunnerImportSource) => unknown /** * Publicly available configuration. */ config: VitestRunnerConfig } ``` When initiating this class, Vitest passes down Vitest config, - you should expose it as a `config` property. ::: warning Vitest also injects an instance of `ViteNodeRunner` as `__vitest_executor` property. You can use it to process files in `importFile` method (this is default behavior of `TestRunner` and `BenchmarkRunner`). `ViteNodeRunner` exposes `executeId` method, which is used to import test files in a Vite-friendly environment. Meaning, it will resolve imports and transform file content at runtime so that Node can understand it. ::: ::: tip Snapshot support and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `BenchmarkNodeRunner`, if you want to extend benchmark functionality. ::: ## Tasks Suites and tests are called `tasks` internally. Vitest runner initiates a `File` task before collecting any tests - this is a superset of `Suite` with a few additional properties. It is available on every task (including `File`) as a `file` property. ```ts interface File extends Suite { /** * The name of the pool that the file belongs to. * @default 'forks' */ pool?: string /** * The path to the file in UNIX format. */ filepath: string /** * The name of the workspace project the file belongs to. */ projectName: string | undefined /** * The time it took to collect all tests in the file. * This time also includes importing all the file dependencies. */ collectDuration?: number /** * The time it took to import the setup file. */ setupDuration?: number /** * Whether the file is initiated without running any tests. * This is done to populate state on the server side by Vitest. */ local?: boolean } ``` Every suite has a `tasks` property that is populated during collection phase. It is useful to traverse the task tree from the top down. ```ts interface Suite extends TaskBase { type: 'suite' /** * File task. It's the root task of the file. */ file: File /** * An array of tasks that are part of the suite. */ tasks: Task[] } ``` Every task has a `suite` property that references a suite it is located in. If `test` or `describe` are initiated at the top level, they will not have a `suite` property (it will **not** be equal to `file`!). `File` also never has a `suite` property. It is useful to travers the tasks from the bottom up. ```ts interface Test extends TaskBase { type: 'test' /** * Test context that will be passed to the test function. */ context: TaskContext & ExtraContext & TestContext /** * File task. It's the root task of the file. */ file: File /** * Whether the task was skipped by calling `t.skip()`. */ pending?: boolean /** * Whether the task should succeed if it fails. If the task fails, it will be marked as passed. */ fails?: boolean /** * Hooks that will run if the task fails. The order depends on the `sequence.hooks` option. */ onFailed?: OnTestFailedHandler[] /** * Hooks that will run after the task finishes. The order depends on the `sequence.hooks` option. */ onFinished?: OnTestFinishedHandler[] /** * Store promises (from async expects) to wait for them before finishing the test */ promises?: Promise[] } ``` Every task can have a `result` field. Suites can only have this field if an error thrown within a suite callback or `beforeAll`/`afterAll` callbacks prevents them from collecting tests. Tests always have this field after their callbacks are called - the `state` and `errors` fields are present depending on the outcome. If an error was thrown in `beforeEach` or `afterEach` callbacks, the thrown error will be present in `task.result.errors`. ```ts export interface TaskResult { /** * State of the task. Inherits the `task.mode` during collection. * When the task has finished, it will be changed to `pass` or `fail`. * - **pass**: task ran successfully * - **fail**: task failed */ state: TaskState /** * Errors that occurred during the task execution. It is possible to have several errors * if `expect.soft()` failed multiple times. */ errors?: ErrorWithDiff[] /** * How long in milliseconds the task took to run. */ duration?: number /** * Time in milliseconds when the task started running. */ startTime?: number /** * Heap size in bytes after the task finished. * Only available if `logHeapUsage` option is set and `process.memoryUsage` is defined. */ heap?: number /** * State of related to this task hooks. Useful during reporting. */ hooks?: Partial> /** * The amount of times the task was retried. The task is retried only if it * failed and `retry` option is set. */ retryCount?: number /** * The amount of times the task was repeated. The task is repeated only if * `repeats` option is set. This number also contains `retryCount`. */ repeatCount?: number } ``` ## Your Task Function Vitest exposes a `Custom` task type that allows users to reuse built-int reporters. It is virtually the same as `Test`, but has a type of `'custom'`. A task is an object that is part of a suite. It is automatically added to the current suite with a `suite.task` method: ```js // ./utils/custom.js import { createTaskCollector, getCurrentSuite, setFn } from 'vitest/suite' export { describe, beforeAll, afterAll } from 'vitest' // this function will be called during collection phase: // don't call function handler here, add it to suite tasks // with "getCurrentSuite().task()" method // note: createTaskCollector provides support for "todo"/"each"/... export const myCustomTask = createTaskCollector( function (name, fn, timeout) { getCurrentSuite().task(name, { ...this, // so "todo"/"skip"/... is tracked correctly meta: { customPropertyToDifferentiateTask: true }, handler: fn, timeout, }) } ) ``` ```js // ./garden/tasks.test.js 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() }) }) ``` ```bash vitest ./garden/tasks.test.js ``` ::: warning If you don't have a custom runner or didn't define `runTest` method, Vitest will try to retrieve a task automatically. If you didn't add a function with `setFn`, it will fail. :::