Test Context & Fixtures
Built-in Context
Every test receives context as its first argument:
test('context', ({ task, expect, skip, signal, annotate }) => {
console.log(task.name) // Test metadata (readonly)
expect(1).toBe(1) // Expect bound to this test
skip(condition, 'reason') // Skip dynamically
})Properties:
task— test metadata (name, file, etc.)expect— expect bound to this test (required for concurrent snapshot tests)skip(condition?, message?)— skip the testsignal(3.2+) —AbortSignalaborted on timeout/cancel/bailannotate(message, type?, attachment?)(3.2+) — attach reporter annotationsonTestFinished(fn)/onTestFailed(fn)— per-test cleanup/handlersbench(v5) — benchmark fixture (only in*.bench.tsfiles)
Custom Fixtures — Builder Pattern (4.1+, recommended)
.extend(name, options?, fixture) infers types automatically. Use onCleanup for teardown:
import { test as baseTest } from 'vitest'
export const test = baseTest
// Plain value — type inferred as { port: number; host: string }
.extend('config', { port: 3000, host: 'localhost' })
// Function fixture — can read previously defined fixtures
.extend('server', async ({ config }, { onCleanup }) => {
const server = await startServer(config)
onCleanup(() => server.close()) // runs after test/scope ends
return server
})
test('uses server', ({ config, server }) => {
expect(server.url).toContain(String(config.port))
})
onCleanupcan be called once per fixture. For multiple resources, split into separate fixtures.
Fixture Options
const test = baseTest
.extend('metrics', { auto: true }, () => new Metrics()) // runs for every test
.extend('config', { scope: 'worker' }, () => loadConfig()) // once per worker
.extend('db', { scope: 'file' }, async ({ config }, { onCleanup }) => {
const db = await createDatabase(config)
onCleanup(() => db.close())
return db
})
.extend('baseUrl', { injected: true }, () => 'http://localhost:3000') // overridable via configObject Syntax (Playwright-compatible)
Uses the use() callback; types must be declared manually:
const test = baseTest.extend<{ page: Page; baseUrl: string }>({
page: async ({}, use) => {
const page = await browser.newPage()
await use(page) // test runs here
await page.close() // cleanup after
},
baseUrl: 'http://localhost:3000',
})Tuple form sets options: fixture: [async ({}, use) => {…}, { scope: 'file' }].
Fixture Scopes (3.2+)
| Scope | Lifetime | Can access |
|---|---|---|
test (default) |
each test | worker + file + test fixtures + built-in context |
file |
once per file | worker + file fixtures |
worker |
once per worker process | only worker fixtures |
Only test-scoped fixtures can access the built-in context (task, expect, …). In file/worker fixtures use expect.getState().testPath for the file path. By default every file is its own worker, so file and worker behave the same unless isolation is disabled.
Injected Fixtures (per-project values)
// fixtures.ts
const test = baseTest.extend('url', { injected: true }, '/default')
// vitest.config.ts — provide per project
defineConfig({
test: {
projects: [
{ test: { name: 'prod', provide: { url: 'https://prod' } } },
],
},
})Read raw provided values without fixtures via import { inject } from 'vitest'.
Overriding Fixtures — test.override (4.1+)
test.override replaces fixture values for a suite and its children (replaces the deprecated test.scoped):
describe('production', () => {
test
.override('config', { port: 8080, host: 'api.example.com' })
.override('debug', false) // chainable
test('uses prod config', ({ server }) => {
expect(server.url).toBe('http://api.example.com:8080')
})
})
// Function override (reads other fixtures) with cleanup
test.override('db', async ({ config }, { onCleanup }) => {
const db = await createTestDatabase(config)
onCleanup(() => db.drop())
return db
})You cannot introduce new fixtures or change scope/auto via override; use test.extend for new fixtures.
Composing & Hooks
Extend an already-extended test, and use type-aware hooks on the extended test:
import { test as dbTest } from './db-test'
export const test = dbTest.extend('user', ({ db }) => db.createUser())
test.beforeEach(({ db }) => db.seed()) // sees fixtures
test.beforeAll(({ db }) => db.migrate()) // file/worker fixtures only (4.1+)
test.aroundAll(async (run, { db }) => db.tx(run))Key Points
- Prefer the builder pattern — types are inferred, cleanup via
onCleanup - Fixtures are lazy — only initialized when destructured
- Always destructure
{ db }(notcontext.db) - Use
{ scope: 'file' | 'worker' }for expensive shared resources - Use
test.override(nottest.scoped) to vary fixture values per suite - Use
{ injected: true }+ projectprovidefor per-project values