Skip to content

Commit 6b21cfe

Browse files
authored
feat: add hooks with type-safe extra context to TestAPI (#8623)
1 parent a96ea14 commit 6b21cfe

File tree

4 files changed

+67
-1
lines changed

4 files changed

+67
-1
lines changed

docs/guide/test-context.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,4 +455,26 @@ test('types are correct', ({
455455
// ...
456456
})
457457
```
458+
458459
:::
460+
461+
When using `test.extend`, the extended `test` object provides type-safe `beforeEach` and `afterEach` hooks that are aware of the new context:
462+
463+
```ts
464+
const test = baseTest.extend<{
465+
todos: number[]
466+
}>({
467+
todos: async ({}, use) => {
468+
await use([])
469+
},
470+
})
471+
472+
// Unlike global hooks, these hooks are aware of the extended context
473+
test.beforeEach(({ todos }) => {
474+
todos.push(1)
475+
})
476+
477+
test.afterEach(({ todos }) => {
478+
console.log(todos)
479+
})
480+
```

packages/runner/src/suite.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
withTimeout,
3434
} from './context'
3535
import { mergeContextFixtures, mergeScopedFixtures, withFixtures } from './fixture'
36+
import { afterAll, afterEach, beforeAll, beforeEach } from './hooks'
3637
import { getHooks, setFn, setHooks, setTestFixture } from './map'
3738
import { getCurrentTest } from './test-state'
3839
import { findTestFileStackTrace } from './utils'
@@ -794,6 +795,11 @@ export function createTaskCollector(
794795
}, _context)
795796
}
796797

798+
taskFn.beforeEach = beforeEach
799+
taskFn.afterEach = afterEach
800+
taskFn.beforeAll = beforeAll
801+
taskFn.afterAll = afterAll
802+
797803
const _test = createChainable(
798804
['concurrent', 'sequential', 'skip', 'only', 'todo', 'fails'],
799805
taskFn,

packages/runner/src/types/tasks.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { Awaitable, TestError } from '@vitest/utils'
22
import type { FixtureItem } from '../fixture'
3+
import type { afterAll, afterEach, beforeAll, beforeEach } from '../hooks'
34
import type { ChainableFunction } from '../utils/chain'
45

56
export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued'
@@ -482,8 +483,15 @@ interface ExtendedAPI<ExtraContext> {
482483
runIf: (condition: any) => ChainableTestAPI<ExtraContext>
483484
}
484485

486+
interface Hooks<ExtraContext> {
487+
beforeAll: typeof beforeAll
488+
afterAll: typeof afterAll
489+
beforeEach: typeof beforeEach<ExtraContext>
490+
afterEach: typeof afterEach<ExtraContext>
491+
}
492+
485493
export type TestAPI<ExtraContext = object> = ChainableTestAPI<ExtraContext>
486-
& ExtendedAPI<ExtraContext> & {
494+
& ExtendedAPI<ExtraContext> & Hooks<ExtraContext> & {
487495
extend: <T extends Record<string, any> = object>(
488496
fixtures: Fixtures<T, ExtraContext>
489497
) => TestAPI<{

test/core/test/test-extend.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,3 +497,33 @@ describe('suite with timeout', () => {
497497
expect(task.timeout).toBe(1_000)
498498
})
499499
}, 100)
500+
501+
describe('type-safe fixture hooks', () => {
502+
const counterTest = test.extend<{
503+
counter: { value: number }
504+
fileCounter: { value: number }
505+
}>({
506+
counter: async ({}, use) => { await use({ value: 0 }) },
507+
fileCounter: [async ({}, use) => { await use({ value: 0 }) }, { scope: 'file' }],
508+
})
509+
510+
counterTest.beforeEach(({ counter }) => {
511+
// shouldn't have typescript error because of 'counter' here
512+
counter.value += 1
513+
})
514+
515+
counterTest.afterEach(({ fileCounter }) => {
516+
// shouldn't have typescript error because of 'fileCounter' here
517+
fileCounter.value += 2
518+
})
519+
520+
// beforeAll and afterAll hooks are not tested here, because they don't provide an extra context
521+
522+
counterTest('beforeEach fixture hook can adapt type-safe context', ({ counter }) => {
523+
expect(counter.value).toBe(1)
524+
})
525+
526+
counterTest('afterEach fixture hook can adapt type-safe context', ({ fileCounter }) => {
527+
expect(fileCounter.value).toBe(2)
528+
})
529+
})

0 commit comments

Comments
 (0)