---
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)
})
```
#### `context.signal` 3.2.0 {#context-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`](/advanced/api/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 overriden 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
})
```
#### 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 Infering
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
}) => {
// ...
})
```
:::
### `beforeEach` and `afterEach`
::: danger Deprecated
This is an outdated way of extending context and it will not work when the `test` is extended with `test.extend`.
:::
The contexts are different for each test. You can access and extend them within the `beforeEach` and `afterEach` hooks.
```ts
import { beforeEach, it } from 'vitest'
beforeEach(async (context) => {
// extend context
context.foo = 'bar'
})
it('should work', ({ foo }) => {
console.log(foo) // 'bar'
})
```
#### TypeScript
To provide property types for all your custom contexts, you can augment the `TestContext` type by adding
```ts
declare module 'vitest' {
export interface TestContext {
foo?: string
}
}
```
If you want to provide property types only for specific `beforeEach`, `afterEach`, `it` and `test` hooks, you can pass the type as a generic.
```ts
interface LocalTestContext {
foo: string
}
beforeEach(async (context) => {
// typeof context is 'TestContext & LocalTestContext'
context.foo = 'bar'
})
it('should work', ({ foo }) => {
// typeof foo is 'string'
console.log(foo) // 'bar'
})
```