test.extend method overview
The `test.extend` method lets you create a custom test API with fixtures - reusable values that are automatically set up and torn down for your tests. Vitest supports two syntaxes: the builder pattern (recommended) and the object syntax (Playwright-compatible).
Builder pattern: test.extend basic usage
The builder pattern is the recommended way to define fixtures in Vitest 4.1.0+. It provides automatic type inference where TypeScript infers the type of each fixture from its return value. Fixtures are defined by chaining `.extend()` calls, with simple values like `.extend('config', { port: 3000, host: 'localhost' })` or function fixtures like `.extend('server', async ({ config }) => { ... })`.
Fixture setup and cleanup with onCleanup
For fixtures that need setup or cleanup logic using the builder pattern, use a function. The `onCleanup` callback registers teardown logic that runs after the fixture's scope ends. The `onCleanup` function can only be called once per fixture.
Fixture options in builder pattern
The second argument to `.extend()` accepts options: `auto` (boolean) - fixture runs for every test even if not used; `scope` ('worker', 'file', or 'test') - changes when fixture is initialized; `injected` (boolean) - fixture can be overridden via config. For test-scoped fixtures (the default), you can omit the options.
Accessing other fixtures in builder pattern
Each fixture in the builder pattern can access previously defined fixtures via its first parameter. This works for both function and non-function fixtures.
Object syntax for test.extend
Vitest supports a Playwright-compatible object syntax for `test.extend`. With this syntax, fixtures are defined as an object where each property can be a simple value or an async function. The key difference from the builder pattern is the `use()` callback pattern: setup code goes before `await use()`, and cleanup code goes after it.
Object syntax requires manual type declarations
With the object syntax in test.extend, you need to provide types manually as a generic parameter since TypeScript cannot infer them from the `use()` callback.
Tuple syntax for options with object syntax
With the object syntax in test.extend, use a tuple to specify fixture options. The tuple contains the fixture definition (async function or value) and an options object. Example: `fixture: [async ({}, use) => { ... }, { auto: true }]`.
Fixture initialization is smart and lazy
Vitest runner will smartly initialize fixtures and inject them into the test context based on usage. Fixtures that are not used in a test will not be initialized.
Context must be destructured in test.extend
When using `test.extend()` with fixtures, you should always use the object destructuring pattern `{ database }` to access context both in fixture functions and test functions, not pass the context as a single parameter.
Extending already extended tests
You can extend an already extended test to add more fixtures. With the builder pattern, use `.extend()` on the imported test. With the object syntax, use `.extend()` with an object definition on the imported test.
Mixing builder and object syntax
You can combine both approaches in test.extend. The builder pattern can be chained after object-based extensions.
Fixture scopes in test.extend
By default, fixtures are initialized for each test (test scope). You can change this with the `scope` option: 'test' (default, fresh for each test), 'file' (initialized once per test file), or 'worker' (initialized once per worker process).
Fixture scope hierarchy and access rules
Fixtures can only access other fixtures from the same or higher (longer-lived) scopes. Worker fixtures can only access other worker fixtures. File fixtures can access worker and file fixtures. Test fixtures can access worker, file, and test fixtures, plus the built-in test context.
Test-scoped fixtures have access to built-in test context
Test-scoped fixtures have access to the built-in test context properties (`task`, `expect`, `skip`, etc.). Worker and file fixtures run outside of any specific test, so test-specific properties are not available to them.
Type-safe scope access with object syntax
With the object syntax in test.extend, you can use the `$worker`, `$file`, and `$test` keys to explicitly declare which fixtures belong to which scope for compile-time type safety enforcement.
Default fixture with injected option
Since Vitest 3, you can provide different values in different projects by passing `{ injected: true }` in the fixture options. If the key is not specified in the project configuration's `provide` option, the default value will be used.
test.override for fixture value overrides
Since Vitest 4.1.0, you can override fixture values for a specific suite and its children using `test.override`. This returns the test API so calls can be chained. You cannot override a fixture's `scope` or `auto` options, but other options are automatically inherited.
test.override with builder pattern
With the builder pattern, `test.override` can accept either a static value (chainable) or a function that can access other fixtures. For example: `test.override('config', { port: 8080, host: 'api.example.com' })` or `test.override('server', ({ config }) => { ... })`.
test.override with object syntax
With the object syntax, you can use `test.override()` to pass an object with multiple fixtures to override at once.
test.override supports cleanup with onCleanup
When overriding with a function in test.override, you can use `onCleanup` just like in `test.extend` to register cleanup logic.
test.override inheritance in nested scopes
Overrides with `test.override` are inherited by nested suites and can be overwritten again. Inner suites can override values from outer suites.
test.override cannot introduce new fixtures
You cannot introduce new fixtures inside `test.override`. Extend the test context with `test.extend` instead if you need to add new fixtures.
Type-safe hooks with test.extend
When using `test.extend`, the extended `test` object provides type-safe hooks like `test.beforeEach` and `test.afterEach` that are aware of the extended context.
Suite-level hooks with fixtures access in test.extend
Since Vitest 4.1.0, the extended `test` object provides `beforeAll`, `afterAll`, and `aroundAll` hooks that can access file-scoped and worker-scoped fixtures (and auto fixtures).
Suite-level hooks must be called on test.extend object
Suite-level hooks (`beforeAll`, `afterAll`, `aroundAll`) must be called on the `test` object returned from `test.extend()` to have access to the extended fixtures. Using the global functions will not have access to custom fixtures.
Suite-level hooks cannot access test-scoped fixtures
Suite-level hooks can only access file-scoped and worker-scoped fixtures, including auto fixtures. Test-scoped fixtures are not available in these hooks because they run outside the context of individual tests.
Cannot override non-test fixtures inside describe blocks
You cannot use `test.override()` to override fixtures with non-test scopes inside `describe` blocks. Consider overriding at the top level of the module or using the `injected` option with config.
Non-isolate mode affects worker fixture overrides
In non-isolate mode, overriding a `worker` fixture will affect the fixture value in all test files running after it was overridden.
Default fixture scope restriction
By default any fixture without a scope is treated as a `test` fixture. This means you cannot use it inside `worker` and `file` scopes. If you wish to access it there, you must specify a scope manually.
onCleanup can only be called once per fixture
The `onCleanup` function can only be called once per fixture. If you need multiple cleanup operations, either combine them into a single cleanup function, or split your fixture into multiple smaller fixtures.