Test Context
Inspired by Playwright 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.
import { it } from 'vitest'
it('should work', (ctx) => {
// prints name of the test
console.log(ctx.task.name)
})
import { it } from 'vitest'
it('should work', (ctx) => {
// prints name of the test
console.log(ctx.task.name)
})
Built-in Test Context
context.task
A readonly object containing metadata about the test.
context.expect
The expect
API bound to the current test.
Extend Test Context
Vitest provides two diffident ways to help you extend the test context.
test.extend
WARNING
This API is available since Vitest 0.32.3.
Like Playwright, you can use this method to define your own test
API with custom fixtures and reuse it anywhere.
For example, we first create myTest
with two fixtures, todos
and archive
.
// my-test.ts
import { test } from 'vitest'
const todos = []
const archive = []
export const myTest = test.extend({
todos: async ({ task }, 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
})
// my-test.ts
import { test } from 'vitest'
const todos = []
const archive = []
export const myTest = test.extend({
todos: async ({ task }, 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.
import { expect } from 'vitest'
import { myTest } from './my-test.ts'
myTest('add items to todos', ({ todos }) => {
expect(todos.length).toBe(3)
todos.add(4)
expect(todos.length).toBe(4)
})
myTest('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)
})
import { expect } from 'vitest'
import { myTest } from './my-test.ts'
myTest('add items to todos', ({ todos }) => {
expect(todos.length).toBe(3)
todos.add(4)
expect(todos.length).toBe(4)
})
myTest('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 myTest
.
export const myTest2 = myTest.extend({
settings: {
// ...
}
})
export const myTest2 = myTest.extend({
settings: {
// ...
}
})
Fixture initialization
Vitest runner will smartly initialize your fixtures and inject them into the test context based on usage.
import { test } from 'vitest'
async function todosFn({ task }, use) {
await use([1, 2, 3])
}
const myTest = test.extend({
todos: todosFn,
archive: []
})
// todosFn will not run
myTest('', () => {})
myTets('', ({ archive }) => {})
// todosFn will run
myTest('', ({ todos }) => {})
import { test } from 'vitest'
async function todosFn({ task }, use) {
await use([1, 2, 3])
}
const myTest = test.extend({
todos: todosFn,
archive: []
})
// todosFn will not run
myTest('', () => {})
myTets('', ({ archive }) => {})
// todosFn will run
myTest('', ({ 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.
TypeScript
To provide fixture types for all your custom contexts, you can pass the fixtures type as a generic.
interface MyFixtures {
todos: number[]
archive: number[]
}
const myTest = test.extend<MyFixtures>({
todos: [],
archive: []
})
myTest('', (context) => {
expectTypeOf(context.todos).toEqualTypeOf<number[]>()
expectTypeOf(context.archive).toEqualTypeOf<number[]>()
})
interface MyFixtures {
todos: number[]
archive: number[]
}
const myTest = test.extend<MyFixtures>({
todos: [],
archive: []
})
myTest('', (context) => {
expectTypeOf(context.todos).toEqualTypeOf<number[]>()
expectTypeOf(context.archive).toEqualTypeOf<number[]>()
})
beforeEach
and afterEach
The contexts are different for each test. You can access and extend them within the beforeEach
and afterEach
hooks.
import { beforeEach, it } from 'vitest'
beforeEach(async (context) => {
// extend context
context.foo = 'bar'
})
it('should work', ({ foo }) => {
console.log(foo) // 'bar'
})
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 aggregate the TestContext
type by adding
declare module 'vitest' {
export interface TestContext {
foo?: string
}
}
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.
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'
})
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'
})