beforeEach and afterEach hooks
beforeEach runs before every test in the file, and afterEach runs after every test, even if the test fails. These hooks ensure each test starts with a known state and prevent mutations from earlier tests from affecting subsequent ones.
beforeAll and afterAll hooks
beforeAll and afterAll run once for the entire test file. beforeAll runs before all tests and afterAll runs after all tests complete. These are suitable for expensive setup operations like database connections, server startup, or loading large files that would be inefficient to repeat for every test.
Scoping hooks with describe blocks
Hooks defined inside a describe block only apply to tests within that block. Top-level hooks apply to every test in the file. This allows different groups of tests to have different setup and teardown configurations.
Hook execution order with nested describe blocks
Hooks follow a nesting structure where top-level hooks wrap around inner hooks. For each test: beforeAll runs once, then for each test execution: outer beforeEach runs first, then inner beforeEach, then the test, then inner afterEach, then outer afterEach, and finally afterAll runs once at the end. This creates a layered context from broad to narrow on setup and narrow to broad on cleanup.
onTestFinished hook
onTestFinished lets you register a cleanup function right where you create a resource within a test. This keeps setup and cleanup close together in the code, improving readability. You can also return a cleanup function from beforeEach and Vitest will call it after each test.
test.extend for fixtures
test.extend allows defining reusable fixtures that are automatically created for each test and cleaned up afterwards. Fixtures are only initialized when a test actually uses them by destructuring from the context, and they can depend on each other. Call onCleanup within a fixture to register cleanup functions.
setupFiles configuration option
The setupFiles config option points to setup files that run before every test file in the project. Setup files run in a separate phase before the test file is collected, making them suitable for polyfills, global configuration, and custom matchers like expect.extend().
beforeEach with cleanup function return
beforeEach can return a cleanup function that Vitest will call after each test. This pattern keeps related setup and teardown code together, especially useful when initialization and cleanup are closely related operations.
Example: beforeEach and afterEach with shared state
import { afterEach, beforeEach, expect, test } from 'vitest'
let items
beforeEach(() => {
items = ['apple', 'banana', 'cherry']
})
afterEach(() => {
items = []
})
test('items starts with 3 fruits', () => {
expect(items).toHaveLength(3)
})
test('can remove an item', () => {
items.pop()
expect(items).toHaveLength(2)
})
test('can add an item', () => {
items.push('date')
expect(items).toHaveLength(4)
})
Example: beforeAll and afterAll with database
import { afterAll, beforeAll, expect, test } from 'vitest'
let db
beforeAll(async () => {
db = await connectToDatabase()
})
afterAll(async () => {
await db.close()
})
test('can query users', async () => {
const users = await db.query('SELECT * FROM users')
expect(users.length).toBeGreaterThan(0)
})
test('can query products', async () => {
const products = await db.query('SELECT * FROM products')
expect(products.length).toBeGreaterThan(0)
})
Example: describe block scoping hooks
import { beforeEach, describe, expect, test } from 'vitest'
describe('math operations', () => {
let value
beforeEach(() => {
value = 0
})
test('can add', () => {
value += 5
expect(value).toBe(5)
})
test('can subtract', () => {
value -= 3
expect(value).toBe(-3)
})
})
describe('string operations', () => {
let text
beforeEach(() => {
text = 'hello'
})
test('can uppercase', () => {
expect(text.toUpperCase()).toBe('HELLO')
})
})
Example: onTestFinished hook
import { expect, onTestFinished, test } from 'vitest'
test('creates a temporary file', () => {
const file = createTempFile()
onTestFinished(() => {
deleteTempFile(file)
})
expect(file.exists()).toBe(true)
})
Example: test.extend fixtures
import { test as baseTest } from 'vitest'
export const test = baseTest
.extend('db', async ({}, { onCleanup }) => {
const db = await createDatabase()
onCleanup(() => db.close())
return db
})
.extend('user', async ({ db }) => {
return await db.createUser({ name: 'Alice' })
})
// Usage in test file:
import { expect } from 'vitest'
import { test } from './my-test.js'
test('user is created', ({ db, user }) => {
expect(user.name).toBe('Alice')
})