Pulled ALL skills from 15 source repositories: - anthropics/skills: 16 (docs, design, MCP, testing) - obra/superpowers: 14 (TDD, debugging, agents, planning) - coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth) - better-auth/skills: 5 (auth patterns) - vercel-labs/agent-skills: 5 (React, design, Vercel) - antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo) - Plus 13 individual skills from various repos Mosaic Stack is not limited to coding — the Orchestrator and subagents serve coding, business, design, marketing, writing, logistics, analysis, and more. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
245 lines
4.4 KiB
Markdown
245 lines
4.4 KiB
Markdown
---
|
|
name: lifecycle-hooks
|
|
description: beforeEach, afterEach, beforeAll, afterAll, and around hooks
|
|
---
|
|
|
|
# Lifecycle Hooks
|
|
|
|
## Basic Hooks
|
|
|
|
```ts
|
|
import { afterAll, afterEach, beforeAll, beforeEach, test } from 'vitest'
|
|
|
|
beforeAll(async () => {
|
|
// Runs once before all tests in file/suite
|
|
await setupDatabase()
|
|
})
|
|
|
|
afterAll(async () => {
|
|
// Runs once after all tests in file/suite
|
|
await teardownDatabase()
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
// Runs before each test
|
|
await clearTestData()
|
|
})
|
|
|
|
afterEach(async () => {
|
|
// Runs after each test
|
|
await cleanupMocks()
|
|
})
|
|
```
|
|
|
|
## Cleanup Return Pattern
|
|
|
|
Return cleanup function from `before*` hooks:
|
|
|
|
```ts
|
|
beforeAll(async () => {
|
|
const server = await startServer()
|
|
|
|
// Returned function runs as afterAll
|
|
return async () => {
|
|
await server.close()
|
|
}
|
|
})
|
|
|
|
beforeEach(async () => {
|
|
const connection = await connect()
|
|
|
|
// Runs as afterEach
|
|
return () => connection.close()
|
|
})
|
|
```
|
|
|
|
## Scoped Hooks
|
|
|
|
Hooks apply to current suite and nested suites:
|
|
|
|
```ts
|
|
describe('outer', () => {
|
|
beforeEach(() => console.log('outer before'))
|
|
|
|
test('test 1', () => {}) // outer before → test
|
|
|
|
describe('inner', () => {
|
|
beforeEach(() => console.log('inner before'))
|
|
|
|
test('test 2', () => {}) // outer before → inner before → test
|
|
})
|
|
})
|
|
```
|
|
|
|
## Hook Timeout
|
|
|
|
```ts
|
|
beforeAll(async () => {
|
|
await slowSetup()
|
|
}, 30_000) // 30 second timeout
|
|
```
|
|
|
|
## Around Hooks
|
|
|
|
Wrap tests with setup/teardown context:
|
|
|
|
```ts
|
|
import { aroundEach, test } from 'vitest'
|
|
|
|
// Wrap each test in database transaction
|
|
aroundEach(async (runTest) => {
|
|
await db.beginTransaction()
|
|
await runTest() // Must be called!
|
|
await db.rollback()
|
|
})
|
|
|
|
test('insert user', async () => {
|
|
await db.insert({ name: 'Alice' })
|
|
// Automatically rolled back after test
|
|
})
|
|
```
|
|
|
|
### aroundAll
|
|
|
|
Wrap entire suite:
|
|
|
|
```ts
|
|
import { aroundAll, test } from 'vitest'
|
|
|
|
aroundAll(async (runSuite) => {
|
|
console.log('before all tests')
|
|
await runSuite() // Must be called!
|
|
console.log('after all tests')
|
|
})
|
|
```
|
|
|
|
### Multiple Around Hooks
|
|
|
|
Nested like onion layers:
|
|
|
|
```ts
|
|
aroundEach(async (runTest) => {
|
|
console.log('outer before')
|
|
await runTest()
|
|
console.log('outer after')
|
|
})
|
|
|
|
aroundEach(async (runTest) => {
|
|
console.log('inner before')
|
|
await runTest()
|
|
console.log('inner after')
|
|
})
|
|
|
|
// Order: outer before → inner before → test → inner after → outer after
|
|
```
|
|
|
|
## Test Hooks
|
|
|
|
Inside test body:
|
|
|
|
```ts
|
|
import { onTestFailed, onTestFinished, test } from 'vitest'
|
|
|
|
test('with cleanup', () => {
|
|
const db = connect()
|
|
|
|
// Runs after test finishes (pass or fail)
|
|
onTestFinished(() => db.close())
|
|
|
|
// Only runs if test fails
|
|
onTestFailed(({ task }) => {
|
|
console.log('Failed:', task.result?.errors)
|
|
})
|
|
|
|
db.query('SELECT * FROM users')
|
|
})
|
|
```
|
|
|
|
### Reusable Cleanup Pattern
|
|
|
|
```ts
|
|
function useTestDb() {
|
|
const db = connect()
|
|
onTestFinished(() => db.close())
|
|
return db
|
|
}
|
|
|
|
test('query users', () => {
|
|
const db = useTestDb()
|
|
expect(db.query('SELECT * FROM users')).toBeDefined()
|
|
})
|
|
|
|
test('query orders', () => {
|
|
const db = useTestDb() // Fresh connection, auto-closed
|
|
expect(db.query('SELECT * FROM orders')).toBeDefined()
|
|
})
|
|
```
|
|
|
|
## Concurrent Test Hooks
|
|
|
|
For concurrent tests, use context's hooks:
|
|
|
|
```ts
|
|
test.concurrent('concurrent', ({ onTestFinished }) => {
|
|
const resource = allocate()
|
|
onTestFinished(() => resource.release())
|
|
})
|
|
```
|
|
|
|
## Extended Test Hooks
|
|
|
|
With `test.extend`, hooks are type-aware:
|
|
|
|
```ts
|
|
const test = base.extend<{ db: Database }>({
|
|
db: async ({}, use) => {
|
|
const db = await createDb()
|
|
await use(db)
|
|
await db.close()
|
|
},
|
|
})
|
|
|
|
// These hooks know about `db` fixture
|
|
test.beforeEach(({ db }) => {
|
|
db.seed()
|
|
})
|
|
|
|
test.afterEach(({ db }) => {
|
|
db.clear()
|
|
})
|
|
```
|
|
|
|
## Hook Execution Order
|
|
|
|
Default order (stack):
|
|
1. `beforeAll` (in order)
|
|
2. `beforeEach` (in order)
|
|
3. Test
|
|
4. `afterEach` (reverse order)
|
|
5. `afterAll` (reverse order)
|
|
|
|
Configure with `sequence.hooks`:
|
|
|
|
```ts
|
|
defineConfig({
|
|
test: {
|
|
sequence: {
|
|
hooks: 'list', // 'stack' (default), 'list', 'parallel'
|
|
},
|
|
},
|
|
})
|
|
```
|
|
|
|
## Key Points
|
|
|
|
- Hooks are not called during type checking
|
|
- Return cleanup function from `before*` to avoid `after*` duplication
|
|
- `aroundEach`/`aroundAll` must call `runTest()`/`runSuite()`
|
|
- `onTestFinished` always runs, even if test fails
|
|
- Use context hooks for concurrent tests
|
|
|
|
<!--
|
|
Source references:
|
|
- https://vitest.dev/api/hooks.html
|
|
-->
|