6. Fixtures (`test.extend`)
const myTest = test.extend<{ db: Database }>({ db: async ({}, use) => { const db = await connect() await use(db) await db.close() },})
myTest('reads a row', async ({ db }) => { expect(await db.get('x')).toBeDefined()})- Fixture keys are the object’s property names (object-syntax) or the first string argument
to
.extend('name', fn)(builder-syntax, newer). Either way, the fixture’s name is statically the literal key/string, no computed keys expected in idiomatic code, but a[computed]: fnkey would defeat static naming and should be flagged unresolved. - A fixture function’s signature is
(dependencies, use) => ...(object-syntax) or(dependencies, { onCleanup }) => value(builder-syntax).dependenciesis destructured from previously-defined fixtures (including base ones), this is itself a fixture dependency graph the extractor can build from the destructuring pattern’s property names, matched against sibling keys in the same.extend()call and any upstream.extend()in the chain. - Auto vs. lazy initialization: Vitest decides whether to instantiate a fixture based on whether
the test callback (or a downstream fixture) actually uses it, i.e., a runtime
usage-detection mechanism, not purely a static one.
- This is not arity/reflection on the function’s declared params. Vitest’s own docs describe
it as “smartly initialize your fixtures … based on usage” and separately instruct that “you
should always use the object destructuring pattern
{ database }to access context both in fixture function and test function”. - So the detector is understood to work reliably on the destructured-name shape. Whole-object dot
access to a custom fixture (
(ctx) => ctx.db, as opposed to a built-inTestContextmember likectx.expect) is a discouraged, not-guaranteed-detected pattern, not merely a stylistic alternative. { auto: true }forces initialization unconditionally, sidestepping the detector entirely.- What a static analyzer can know: the full fixture graph and its declared dependencies (from source).
- What it cannot fully know: whether a given test transitively pulls in a given fixture’s side effects without also statically re-deriving Vitest’s own usage-detection heuristic against that specific callback’s access pattern. The extractor can approximate the destructured shape (a syntactic pattern-match on the callback’s parameter list) but should treat whole-object access to a fixture name as a weaker, best-effort signal than a destructured one, not an equally reliable mirror of runtime behavior.
- This is not arity/reflection on the function’s declared params. Vitest’s own docs describe
it as “smartly initialize your fixtures … based on usage” and separately instruct that “you
should always use the object destructuring pattern
scope:'test'(default, per-test) /'file'(shared per file) /'worker'(shared per worker process), changes whether two tests sharing a fixture are “isolated” for context-equivalence purposes; afile/worker-scoped fixture means state genuinely carries over between tests, which the context model should represent (two tests using the same file-scoped fixture are not automatically context-independent the way twotest-scoped-fixture tests are).injected: true: value supplied externally via projectprovideconfig; statically the fixture’s value is unknowable (comes from config, not source), record as “external/opaque fixture value.”test.scoped(...)(deprecated 4.1+) /test.override(...)(4.1+, current): both mutate fixture values for the current suite (and nested suites) rather than defining new fixtures; must be called at adescribeblock’s top level, a call site inside a test body or nested callback is invalid and (per docs) not the supported form, so the extractor should anchor these calls to direct children of adescribeblock’s statement list.- Reaching the callback: extended fixtures are merged into the same
TestContextobject ordinary hooks/tests receive, from the test body’s perspective there is no syntactic difference between a built-in context property (expect,task,skip, …) and a user-defined fixture; both are just destructured names. Disambiguating “known Vitest context member” vs. “user fixture” requires cross-referencing the destructured name against (a) the built-inTestContextmember list and (b) the fixture keys visible from thetest.extendchain that produced the localtest/itbinding.
Back to the Vitest surface index.