--- title: Test Context | Guide outline: deep --- # Test Context Inspired by [Playwright Fixtures](https://playwright.dev/docs/test-fixtures), Vitest's test context allows you to define utils, states, and fixtures that can be used in your tests. ## Usage The first argument for each test callback is a test context. ```ts import { it } from 'vitest' it('should work', ({ task }) => { // prints name of the test console.log(task.name) }) ``` ## Built-in Test Context #### `task` A readonly object containing metadata about the test. #### `expect` The `expect` API bound to the current test: ```ts import { it } from 'vitest' it('math is easy', ({ expect }) => { expect(2 + 2).toBe(4) }) ``` This API is useful for running snapshot tests concurrently because global expect cannot track them: ```ts 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` ```ts function skip(note?: string): never function skip(condition: boolean, note?: string): void ``` Skips subsequent test execution and marks test as skipped: ```ts import { expect, it } from 'vitest' it('math is hard', ({ skip }) => { skip() expect(2 + 2).toBe(5) }) ``` Since Vitest 3.1, it accepts a boolean parameter to skip the test conditionally: ```ts it('math is hard', ({ skip, mind }) => { skip(mind === 'foggy') expect(2 + 2).toBe(5) }) ``` #### `annotate` 3.2.0 {#annotate} ```ts function annotate( message: string, attachment?: TestAttachment, ): Promise function annotate( message: string, type?: string, attachment?: TestAttachment, ): Promise ``` Add a [test annotation](/guide/test-annotations) that will be displayed by your [reporter](/config/#reporters). ```ts test('annotations API', async ({ annotate }) => { await annotate('https://github.com/vitest-dev/vitest/pull/7953', 'issues') }) ``` #### `signal` 3.2.0 {#signal} An [`AbortSignal`](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) that can be aborted by Vitest. The signal is aborted in these situations: - Test times out - User manually cancelled the test run with Ctrl+C - [`vitest.cancelCurrentRun`](/api/advanced/vitest#cancelcurrentrun) was called programmatically - Another test failed in parallel and the [`bail`](/config/#bail) flag is set ```ts it('stop request when test times out', async ({ signal }) => { await fetch('/resource', { signal }) }, 2000) ``` #### `onTestFailed` The [`onTestFailed`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. #### `onTestFinished` The [`onTestFinished`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. ## Extend Test Context Vitest provides two different ways to help you extend the test context. ### `test.extend` Like [Playwright](https://playwright.dev/docs/api/class-test#test-extend), you can use this method to define your own `test` API with custom fixtures and reuse it anywhere. For example, we first create the `test` collector with two fixtures: `todos` and `archive`. ```ts [my-test.ts] import { test as baseTest } from 'vitest' const todos = [] const archive = [] export const test = baseTest.extend({ todos: async ({}, use) => { // setup the fixture before each test function todos.push(1, 2, 3) // use the fixture value await use(todos) // cleanup the fixture after each test function todos.length = 0 }, archive }) ``` Then we can import and use it. ```ts [my-test.test.ts] 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) }) ``` We can also add more fixtures or override existing fixtures by extending our `test`. ```ts import { test as todosTest } from './my-test.js' export const test = todosTest.extend({ settings: { // ... } }) ``` #### Fixture initialization Vitest runner will smartly initialize your fixtures and inject them into the test context based on usage. ```ts 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 will not run test('skip', () => {}) test('skip', ({ archive }) => {}) // todos will run test('run', ({ todos }) => {}) ``` ::: warning When using `test.extend()` with fixtures, you should always use the object destructuring pattern `{ todos }` to access context both in fixture function and test function. ```ts test('context must be destructured', (context) => { // [!code --] expect(context.todos.length).toBe(2) }) test('context must be destructured', ({ todos }) => { // [!code ++] expect(todos.length).toBe(2) }) ``` ::: #### Automatic fixture Vitest also supports the tuple syntax for fixtures, allowing you to pass options for each fixture. For example, you can use it to explicitly initialize a fixture, even if it's not being used in tests. ```ts import { test as base } from 'vitest' const test = base.extend({ fixture: [ async ({}, use) => { // this function will run setup() await use() teardown() }, { auto: true } // Mark as an automatic fixture ], }) test('works correctly') ``` #### Default fixture Since Vitest 3, you can provide different values in different [projects](/guide/projects). To enable this feature, pass down `{ injected: true }` to the options. If the key is not specified in the [project configuration](/config/#provide), then the default value will be used. :::code-group ```ts [fixtures.test.ts] import { test as base } from 'vitest' const test = base.extend({ url: [ // default value if "url" is not defined in the config '/default', // mark the fixture as "injected" to allow the override { injected: true }, ], }) test('works correctly', ({ url }) => { // url is "/default" in "project-new" // url is "/full" in "project-full" // url is "/empty" in "project-empty" }) ``` ```ts [vitest.config.ts] 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', }, }, }, ], }, }) ``` ::: #### Scoping Values to Suite 3.1.0 {#scoping-values-to-suite} Since Vitest 3.1, you can override context values per suite and its children by using the `test.scoped` API: ```ts 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` uses the new overridden value that is scoped // to all tests in this suite expect(dependant).toEqual({ dependency: 'new' }) }) describe('keeps using scoped value', () => { test('uses scoped value', ({ dependant }) => { // nested suite inherited the value expect(dependant).toEqual({ dependency: 'new' }) }) }) }) test('keep using the default values', ({ dependant }) => { // the `dependency` is using the default // value outside of the suite with .scoped expect(dependant).toEqual({ dependency: 'default' }) }) ``` This API is particularly useful if you have a context value that relies on a dynamic variable like a database connection: ```ts 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 }) ``` #### Per-Scope Context 3.2.0 You can define context that will be initiated once per file or a worker. It is initiated the same way as a regular fixture with an objects parameter: ```ts import { test as baseTest } from 'vitest' export const test = baseTest.extend({ perFile: [ ({}, use) => use([]), { scope: 'file' }, ], perWorker: [ ({}, use) => use([]), { scope: 'worker' }, ], }) ``` The value is initialised the first time any test has accessed it, unless the fixture options have `auto: true` - in this case the value is initialised before any test has run. ```ts const test = baseTest.extend({ perFile: [ ({}, use) => use([]), { scope: 'file', // always run this hook before any test auto: true }, ], }) ``` ::: warning The built-in [`task`](#task) test context is **not available** in file-scoped or worker-scoped fixtures. These fixtures receive a different context object (file or worker context) that does not include test-specific properties like `task`. If you need access to file-level metadata like the file path, use `expect.getState().testPath` instead. ::: The `worker` scope will run the fixture once per worker. The number of running workers depends on various factors. By default, every file runs in a separate worker, so `file` and `worker` scopes work the same way. However, if you disable [isolation](/config/#isolate), then the number of workers is limited by the [`maxWorkers`](/config/#maxworkers) configuration. Note that specifying `scope: 'worker'` when running tests in `vmThreads` or `vmForks` will work the same way as `scope: 'file'`. This limitation exists because every test file has its own VM context, so if Vitest were to initiate it once, one context could leak to another and create many reference inconsistencies (instances of the same class would reference different constructors, for example). #### TypeScript To provide fixture types for all your custom contexts, you can pass the fixtures type as a generic. ```ts interface MyFixtures { todos: number[] archive: number[] } const test = baseTest.extend({ todos: [], archive: [] }) test('types are defined correctly', ({ todos, archive }) => { expectTypeOf(todos).toEqualTypeOf() expectTypeOf(archive).toEqualTypeOf() }) ``` ::: info Type Inferring Note that Vitest doesn't support infering the types when the `use` function is called. It is always preferable to pass down the whole context type as the generic type when `test.extend` is called: ```ts 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 }) => { // ... }) ``` ::: When using `test.extend`, the extended `test` object provides type-safe `beforeEach` and `afterEach` hooks that are aware of the new context: ```ts const test = baseTest.extend<{ todos: number[] }>({ todos: async ({}, use) => { await use([]) }, }) // Unlike global hooks, these hooks are aware of the extended context test.beforeEach(({ todos }) => { todos.push(1) }) test.afterEach(({ todos }) => { console.log(todos) }) ```