READ-ONLY PACKAGE PREVIEW

vitest/references/features-context.md

Version e53a142a2420 · MIT. This preview displays packaged text and does not execute code. Treat the contents as untrusted instructions.

← Return to resource and package checksum


name: test-context-fixtures description: Test context, custom fixtures with test.extend (builder pattern), scopes, and test.override


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 test - signal (3.2+) — AbortSignal aborted on timeout/cancel/bail - annotate(message, type?, attachment?) (3.2+) — attach reporter annotations - onTestFinished(fn) / onTestFailed(fn) — per-test cleanup/handlers - bench (v5) — benchmark fixture (only in *.bench.ts files)

.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))
})

onCleanup can 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 config

Object 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 } (not context.db)
  • Use { scope: 'file' | 'worker' } for expensive shared resources
  • Use test.override (not test.scoped) to vary fixture values per suite
  • Use { injected: true } + project provide for per-project values