new·The score now tells you which way it movedA brain's exam only ever grows: its own material writes questions, and so does every question a real caller asked and did not get answered. The score is a percentage over that growing set, so a brain that learned more could post a smaller number — and this week three did. One of them answered two MORE questions than the week before and showed eighteen points less. Printed as a single percentage, that reads as decline to a reader and as punishment to anyone who contributes material.all news →
mozg.beta
Sign in

Vitest · Guide · all subjects

advanced/lifecycle

125 notes in this subject, read out of this brain and free to use. This is page 1 of 3.

Global setup and teardown example

Global setup and teardown can be implemented in globalSetup files. A setup() function runs once before all tests and can use project.provide() to share data with tests. A teardown() function runs once after all tests for cleanup. Example: export function setup(project) { console.log('Global setup') project.provide('apiUrl', 'http://localhost:3000') } export function teardown() { console.log('Global teardown') }

Performance optimization: use Global Setup for expensive one-time operations

Global setup is ideal for expensive one-time operations such as database seeding or server startup because it runs only once per Vitest run in the main process.

Test file execution order configuration

Test files are executed sequentially by default within a worker, but run in parallel across different workers as configured by maxWorkers. The execution order can be randomized with sequence.shuffle or fine-tuned with sequence.sequencer. Long-running tests typically start earlier based on cache unless shuffle is enabled.

Test File Setup phase: setup files execution

Before each test file runs, setup files configured in setupFiles are executed in the same process as the tests. By default, setup files run in parallel and execute before each test file. Any global state or configuration can be initialized here. If isolation is disabled, setup files still rerun before each test file to trigger side effects, but imported modules are cached. Editing a setup file triggers a rerun of all tests in watch mode.

Reporting phase: lifecycle events and result collection

Throughout the test run, reporters receive lifecycle events as tests progress. Results are collected and formatted, test summaries are generated, and coverage reports are generated if enabled. See the Reporters guide for detailed information about the reporter lifecycle.

Performance consideration: avoid heavy operations in Setup Files

Setup files run before each test file, so avoid heavy operations there if you have many test files. Global setup or beforeAll are better alternatives for expensive setup that needs to run less frequently.

Nested describe blocks: hierarchical hook wrapping

When using nested describe blocks, hooks follow a hierarchical pattern where parent hooks wrap child hooks. aroundAll and aroundEach hooks from outer describe blocks wrap around the corresponding hooks in inner describe blocks, creating nested wrapping behavior.

Hook execution order within a test file

Within each test file, execution follows this order: file-level code runs immediately, describe blocks are processed and tests are registered, then aroundAll hooks wrap around all tests, beforeAll hooks run once before any tests, then for each test: aroundEach hooks wrap the test, beforeEach hooks execute in order defined or based on sequence.hooks, the test function executes, afterEach hooks execute in reverse order by default, cleanup functions from beforeEach hooks execute in reverse order by default, onTestFinished callbacks run in reverse order, and if the test failed onTestFailed callbacks run. After all tests in a suite: afterAll hooks run once and cleanup functions from beforeAll hooks run once. If repeats or retry are set, these steps execute again.

Performance consideration: beforeAll vs beforeEach

beforeAll is better than beforeEach for expensive setup that doesn't need isolation between tests because beforeAll runs once per suite while beforeEach runs before each test.

Nested describe blocks execution flow example

The example shows how nested describe blocks execute with hierarchical hook wrapping. Outer aroundAll runs before all outer tests and nested tests, outer beforeAll runs, then outer aroundEach wraps outer tests, then when nested describe is reached inner aroundAll runs, inner beforeAll runs, outer aroundEach wraps inner aroundEach, then for inner test: outer beforeEach, inner beforeEach, inner test, inner afterEach, outer afterEach, then cleanup functions and aroundEach afters run in reverse order.

Lifecycle scopes and test context access

Different lifecycle phases execute in different scopes with different access to test context. Config files run in the main process with no test context access. Global setup and teardown run in the main process with no test context access (use provide/inject instead). Setup files run in the worker with test context access. File-level code, aroundAll, beforeAll/afterAll, aroundEach, beforeEach/afterEach, and test functions run in the worker with test context access.

Global Setup phase: one-time setup before tests

The Global Setup phase runs once before any test workers are created, executing setup() functions (or exported default function) from configured globalSetup files sequentially. Multiple global setup files run in the order they are defined. Global setup runs in a different global scope from tests, so tests cannot access variables defined in global setup. Use provide/inject instead to share data with tests. Global setup only runs if there is at least one test queued.

Worker Creation: spawning test workers

After global setup completes, Vitest creates test workers based on pool configuration (threads, forks, vmThreads, or vmForks). Each worker gets its own isolated environment unless isolation is disabled. By default, workers are not reused to provide isolation. Workers are only reused if isolation is disabled, or if pool is vmThreads or vmForks because VM provides sufficient isolation.

Complete hook execution flow example

The example shows the complete execution order with file-level code, describe blocks, aroundAll, beforeAll with cleanup, aroundEach for each test, beforeEach with cleanup, test function, afterEach, and afterAll. The output demonstrates that file-level code and suite definition happen during collection, aroundAll before wraps around all tests, beforeAll runs once, then for each test: aroundEach before, beforeEach, test, afterEach, and beforeEach cleanup run, then aroundEach after, then afterAll and beforeAll cleanup run after all tests, then aroundAll after.

Seven main test run lifecycle phases

A typical Vitest test run goes through these main phases in order: Initialization (configuration loading and project setup), Global Setup (one-time setup before any tests run), Worker Creation (test workers are spawned based on pool configuration), Test File Collection (test files are discovered and organized), Test Execution (tests run with their hooks and assertions), Reporting (results are collected and reported), and Global Teardown (final cleanup after all tests complete). Phases 4–6 run once for each test file, so they execute multiple times across the test suite and may run in parallel across different files when using more than 1 worker.

aroundEach hook: wraps each individual test

The aroundEach hook wraps around each individual test. It receives a runTest function that must be called to execute the wrapped test. aroundEach hooks run before and after each test's beforeEach, test function, and afterEach.

aroundAll hook: wraps all tests in a suite

The aroundAll hook wraps around all tests in a describe suite. It receives a runSuite function that must be called to execute the wrapped tests. aroundAll hooks from parent describe blocks wrap around child describe blocks' hooks.

Concurrent tests: parallel execution within a file

When using test.concurrent or sequence.concurrent, tests within the same file can run in parallel. Each concurrent test still runs its own beforeEach and afterEach hooks independently. Use test context for concurrent snapshots: test.concurrent('name', async ({ expect }) => {}).

Global Teardown phase: cleanup after all tests

After all tests complete, teardown() functions from configured globalSetup files run in reverse order of their setup. In watch mode, teardown runs before process exit, not between test reruns. Global teardown executes in the main process.

Performance consideration: disabling isolation and setup files

Disabling isolation improves performance, but setup files still execute before each test file even with isolation disabled. Imported modules are cached when isolation is disabled.

Initialization phase: configuration and validation

During the Initialization phase, Vitest loads the configuration file, parses command-line arguments, and validates the project structure. This phase runs in the main process before any test workers are created. The phase can run again if the config file or one of its imports changes.

Test Collection and Execution: file-level code runs immediately

All code outside describe blocks runs immediately during the collection phase when the test file is imported. This includes console.log statements and other side effects at the file level.

Watch mode lifecycle: reruns on file changes

In watch mode, the lifecycle repeats with differences: the initial run follows the full lifecycle, on file changes a new test run starts and only affected test files are re-run with their setup files running again (but global setup does not re-run), and on exit global teardown executes before process termination. Use project.onTestsRerun for rerun-specific logic instead of relying on global setup to re-run.

Setup file example with afterEach hook

Setup files run before each test file and can register hooks. Example: import { afterEach } from 'vitest' console.log('Setup file executing') afterEach(() => { cleanup() })

test.concurrent modifier

Use the concurrent modifier on individual tests to run them concurrently within the same file if they are independent. When tests are marked as concurrent, Vitest groups them together and runs them with Promise.all.

Test parallelism within a file

Within a single file, Vitest runs tests sequentially by default. Tests execute in the order they are defined, one after another. This is the safest default because tests within a file often share setup and state through lifecycle hooks.

sequence.hooks controls hook execution with concurrent tests

The sequence.hooks configuration controls the hook execution order when tests run concurrently. With sequence.hooks set to 'parallel', hooks are also bounded by the maxConcurrency limit.

Opting out of concurrency example

Example of opting out of concurrency for tests that use shared resources: ```ts test('uses a shared resource', { concurrent: false }, async () => { // ... }) describe('shared resource suite', { concurrent: false }, () => { test('step 1', async () => { /* ... */ }) test('step 2', async () => { /* ... */ }) }) ```

Opt out of concurrency with concurrent: false

You can opt individual tests or suites out of inherited concurrency by setting concurrent: false. This applies to both individual tests and describe blocks.

sequence.concurrent config for global concurrency

Set sequence.concurrent to true in config to make all tests in the project run concurrently by default.

describe.concurrent for suite concurrency

Apply concurrent to an entire suite using describe.concurrent. All tests within that suite will run concurrently if they are independent.

Concurrent only helps with async operations

Concurrent tests only speed things up when tests spend time waiting on network requests, timers, file I/O, etc. Purely synchronous tests will not benefit because they still block the single JavaScript thread.

Concurrent tests example

Example of running independent async tests concurrently: ```ts import { expect, test } from 'vitest' test.concurrent('fetches user profile', async () => { const user = await fetchUser(1) expect(user.name).toBe('Alice') }) test.concurrent('fetches user posts', async () => { const posts = await fetchPosts(1) expect(posts).toHaveLength(3) }) ```

maxWorkers controls file parallelism

The maxWorkers option controls how many workers run simultaneously. More workers means more files run in parallel, but also more memory and CPU usage. The right number depends on your machine and how heavy your tests are.

Pool configuration for file parallelism

The pool configuration determines how Vitest creates workers: forks (the default) and vmForks run each file in a separate child process; threads and vmThreads run each file in a separate worker thread.

File parallelism default behavior

By default, Vitest runs test files in parallel across multiple workers. Each file gets its own isolated environment, so tests in different files cannot interfere with each other.

maxConcurrency limits concurrent tests

The maxConcurrency option bounds the number of concurrent tests running at once within a single file.

describe.concurrent example

Example of running all tests in a suite concurrently: ```ts import { describe, expect, test } from 'vitest' describe.concurrent('user API', () => { test('fetches profile', async () => { const user = await fetchUser(1) expect(user.name).toBe('Alice') }) test('fetches posts', async () => { const posts = await fetchPosts(1) expect(posts).toHaveLength(3) }) }) ```

Disable file parallelism with fileParallelism config

Set fileParallelism to false to run files one at a time instead of in parallel. This is useful when tests share an external resource like a database that cannot handle concurrent access.

Hooks behavior with concurrent tests

When tests run concurrently, beforeAll and afterAll still run once for the group, but beforeEach and afterEach run for each test — potentially at the same time, since the tests themselves overlap.

Forwarding signal to helper functions

To ensure cancellation propagates through custom helper functions, wire the test's signal into your own helpers. Example: ```ts async function pollUntilReady(url: string, signal: AbortSignal) { while (!signal.aborted) { const res = await fetch(url, { signal }) if (res.ok) { return } await new Promise(r => setTimeout(r, 200)) } signal.throwIfAborted() } test('worker becomes ready', async ({ signal }) => { await pollUntilReady('http://localhost:4000/health', signal) }, 5000) ``` This pattern ensures that when the test is cancelled, the abort signal propagates all the way down through the helper function.

Web APIs that accept AbortSignal

The following Web APIs accept an AbortSignal for cancellation: fetch, addEventListener (with { signal } option to remove listener on abort), and ReadableStream.pipeTo. Node.js APIs that accept AbortSignal include fs.readFile, child_process.spawn, setTimeout, and setInterval (all with { signal } option). Custom code can also support abort signals by calling signal.throwIfAborted() or listening for 'abort' events.

Fetch with AbortSignal example

Example of using the test signal with fetch to stop requests when a test times out: ```ts import { test } from 'vitest' test('stop request when test times out', async ({ signal }) => { await fetch('/heavy-resource', { signal }) }, 2000) ``` If the request hasn't completed within 2 seconds, fetch rejects with AbortError instead of the test hanging until the operation finishes.

Test context signal for cancellation

The test context provides a `signal` property that fires when a test is cancelled by Vitest. This includes when a test exceeds its timeout, when another test fails under --bail, or when Ctrl+C is pressed in the terminal. The signal can be passed to any function that accepts an AbortSignal to properly release resources when the test is cancelled.

Database transaction benefits for integration tests

Wrapping each database integration test in a transaction that rolls back when finished avoids the need to truncate tables between tests, which is slow. Nothing ever commits to the database, and there is no per-test cleanup code to write.

aroundEach hook availability

The `aroundEach` hook is available in Vitest version 4.1.0 or later.

Scoped fixtures availability

Scoped fixtures are available in Vitest version 3.2.0 or later.

Database transaction per test pattern with fixture

Use a scoped fixture with `scope: 'file'` to create a database instance once per file. Combine with `aroundEach` hook to wrap every test in a transaction that rolls back automatically when the test finishes. The fixture returns a database instance and uses `onCleanup` to close the connection when the file is done.

One connection per worker with scope: 'worker'

To share a single database connection across multiple test files in the same worker, use `scope: 'worker'` on the fixture and set `isolate: false` in the Vitest config. This reduces the number of connections needed. For a suite of 200 files on 8 workers, this creates 8 connections instead of 200.

Database transaction per test code example

```ts import { test as baseTest } from 'vitest' import { createTestDatabase } from './db.ts' export const test = baseTest .extend('db', { scope: 'file' }, async ({}, { onCleanup }) => { const db = await createTestDatabase() onCleanup(() => db.close()) return db }) test.aroundEach(async (runTest, { db }) => { await db.transaction(runTest) }) test('insert user', async ({ db }) => { await db.insert({ name: 'Alice' }) // rolled back automatically when the test ends }) ``` This example shows how to set up a database fixture that wraps each test in a transaction.

vi.doMock with using keyword example

Example of using `vi.doMock` with the `using` keyword: `using _mock = vi.doMock('./users', () => ({ loadUser: () => ({ id: '1', name: 'Alice' }) })); const { loadUser } = await import('./users'); expect(loadUser('alice').name).toBe('Alice');` - the module is unmocked when the block exits.

onTestFinished as alternative to using for whole-test cleanup

If your environment does not support Explicit Resource Management, `onTestFinished` is the closest equivalent for whole-test cleanup. It registers cleanup inline and runs after the test completes regardless of pass or failure, but cannot tear down a spy mid-test like `using` can.

Explicit Resource Management support requirements

The `using` keyword requires Explicit Resource Management support: TypeScript 5.2 or higher (with `target: 'es2022'` or higher and the disposable lib included by default), or Node.js 24 or higher (Node.js 22+ requires --harmony-style flags for native runtime support).

using keyword block-scoped spy cleanup

The `using` keyword is block-scoped, allowing you to install a spy for just part of a test without affecting the rest. This capability is unique to `using` and cannot be replicated with `afterEach` or `onTestFinished`, which run after the entire test completes.

onTestFinished hook for inline cleanup

Example of `onTestFinished` for cleanup: `const spy = vi.spyOn(console, 'log').mockImplementation(() => {}); onTestFinished(() => spy.mockRestore()); debug('message'); expect(spy).toHaveBeenCalled());` - cleanup is registered inline and runs after the test.

using keyword for automatic spy restoration

The `using` keyword (Explicit Resource Management) automatically restores spies and mocks when the block exits, eliminating the need for `afterEach` or `onTestFinished` cleanup. This works with `vi.spyOn`, `vi.fn`, and `vi.doMock`. When declared with `using` instead of `const`, restoration happens automatically.

State leak between tests from unrestore mocks

Spies and mocks need to be restored after the test that installed them, otherwise state leaks between tests. This is why cleanup is necessary, either via `afterEach`, `onTestFinished`, or the `using` keyword.

vi.spyOn with using keyword example

Example of using `vi.spyOn` with the `using` keyword for automatic restoration: `using spy = vi.spyOn(console, 'log').mockImplementation(() => {}); debug('message'); expect(spy).toHaveBeenCalled();` - the console.log is restored when the block exits without needing an afterEach.

Block-scoped mock with using example

Example of scoping a mock to only part of a test: `using fetchSpy = vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('{"ok":true}')); await login('alice', 'secret'); expect(fetchSpy).toHaveBeenCalledOnce();` - the real fetch is available before and after the block.

watchTriggerPatterns config option

watchTriggerPatterns makes dependencies on non-imported files explicit. It was added in version 3.2.0. You declare a regex pattern over file paths and a callback function that returns which tests to rerun when a matching file changes. This solves the problem where Vitest only tracks the import graph and misses tests that depend on files they don't import, such as email templates loaded with fs.readFile, JSON fixtures parsed at runtime, or HTML/CSS pulled in by build steps.

Give your agent this brain