From fe16c81e2dd3e7f1d64127142f489866eb41b3bb Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 3 Feb 2026 13:55:57 +0100 Subject: [PATCH 1/3] feat(fixtures): pass down file context to `beforeAll`/`afterAll` (#9572) --- api/hooks.md | 18 ++++++---- guide/test-context.md | 84 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 84 insertions(+), 18 deletions(-) diff --git a/api/hooks.md b/api/hooks.md index 94ba3c1c..311f0363 100644 --- a/api/hooks.md +++ b/api/hooks.md @@ -12,7 +12,7 @@ Test hooks are called in a stack order ("after" hooks are reversed) by default, ```ts function beforeEach( - body: () => unknown, + body: (context: TestContext) => unknown, timeout?: number, ): void ``` @@ -54,7 +54,7 @@ beforeEach(async () => { ```ts function afterEach( - body: () => unknown, + body: (context: TestContext) => unknown, timeout?: number, ): void ``` @@ -82,7 +82,7 @@ You can also use [`onTestFinished`](#ontestfinished) during the test execution t ```ts function beforeAll( - body: () => unknown, + body: (context: ModuleContext) => unknown, timeout?: number, ): void ``` @@ -122,7 +122,7 @@ beforeAll(async () => { ```ts function afterAll( - body: () => unknown, + body: (context: ModuleContext) => unknown, timeout?: number, ): void ``` @@ -146,7 +146,10 @@ Here the `afterAll` ensures that `stopMocking` method is called after all tests ```ts function aroundEach( - body: (runTest: () => Promise, context: TestContext) => Promise, + body: ( + runTest: () => Promise, + context: TestContext, + ) => Promise, timeout?: number, ): void ``` @@ -253,7 +256,10 @@ test('insert user', async ({ db, user }) => { ```ts function aroundAll( - body: (runSuite: () => Promise) => Promise, + body: ( + runSuite: () => Promise, + context: ModuleContext, + ) => Promise, timeout?: number, ): void ``` diff --git a/guide/test-context.md b/guide/test-context.md index baa53c4c..f416aa47 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -278,14 +278,6 @@ const test = baseTest .extend('simple', () => 'value') ``` -Non-function values only support the `injected` option: - -```ts -const test = baseTest - .extend('baseUrl', { injected: true }, 'http://localhost:3000') - .extend('defaults', { port: 3000, host: 'localhost' }) -``` - #### Accessing Other Fixtures Each fixture can access previously defined fixtures via its first parameter. This works for both function and non-function fixtures: @@ -504,15 +496,13 @@ Note that you cannot override non-test fixtures inside `describe` blocks: ```ts test.describe('a nested suite', () => { - test.override('port', 3000) // throws an error + test.override('port', { scope: 'worker' }, 3000) // throws an error }) ``` Consider overriding it on the top level of the module, or by using [`injected`](#default-fixture-injected) option and providing the value in the project config. Also note that in [non-isolate](/config/isolate) mode overriding a `worker` fixture will affect the fixture value in all test files running after it was overriden. - - ::: #### Test Scope (Default) @@ -830,7 +820,7 @@ Note that you cannot introduce new fixtures inside `test.override`. Extend the t ### Type-Safe Hooks -When using `test.extend`, the extended `test` object provides type-safe `beforeEach` and `afterEach` hooks that are aware of the new context: +When using `test.extend`, the extended `test` object provides type-safe hooks that are aware of the extended context: ```ts const test = baseTest @@ -845,3 +835,73 @@ test.afterEach(({ counter }) => { console.log('Final count:', counter.value) }) ``` + +#### Suite-Level Hooks with Fixtures 4.1.0 {#suite-level-hooks} + +The extended `test` object also provides [`beforeAll`](/api/hooks#beforeall), [`afterAll`](/api/hooks#afterall), and [`aroundAll`](/api/hooks#aroundall) hooks that can access file-scoped and worker-scoped fixtures: + +```ts +const test = baseTest + .extend('config', { scope: 'file' }, () => loadConfig()) + .extend('database', { scope: 'file' }, async ({ config }, { onCleanup }) => { + const db = await createDatabase(config) + onCleanup(() => db.close()) + return db + }) + +// Access file-scoped fixtures in suite-level hooks +test.aroundAll(async (runSuite, { database }) => { + await database.transaction(runSuite) +}) + +test.beforeAll(async ({ database }) => { + await database.createUsers() +}) + +test.afterAll(async ({ database }) => { + await database.removeUsers() +}) +``` + +::: warning IMPORTANT +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 `beforeAll`/`afterAll`/`aroundAll` functions will not have access to your custom fixtures: + +```ts +import { test as baseTest, beforeAll } from 'vitest' + +const test = baseTest + .extend('database', { scope: 'file' }, async ({}, { onCleanup }) => { + const db = await createDatabase() + onCleanup(() => db.close()) + return db + }) + +// ❌ WRONG: Global beforeAll doesn't have access to 'database' +beforeAll(({ database }) => { + // Error: 'database' is undefined +}) + +// ✅ CORRECT: Use test.beforeAll to access fixtures +test.beforeAll(({ database }) => { + // 'database' is available +}) +``` + +This applies to all suite-level hooks: `beforeAll`, `afterAll`, and `aroundAll`. +::: + +::: tip +Suite-level hooks can only access [**file-scoped** and **worker-scoped** fixtures](#fixture-scopes). Test-scoped fixtures are not available in these hooks because they run outside the context of individual tests. If you try to access a test-scoped fixture in a suite-level hook, Vitest will throw an error. + +```ts +const test = baseTest + .extend('testFixture', () => 'test-scoped') + .extend('fileFixture', { scope: 'file' }, () => 'file-scoped') + +// ❌ Error: test-scoped fixtures not available in beforeAll +test.beforeAll(({ testFixture }) => {}) + +// ✅ Works: file-scoped fixtures are available +test.beforeAll(({ fileFixture }) => {}) +``` +::: From 6777bfb4d65ef81facc3a8f09bbe129e90871655 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Wed, 4 Feb 2026 01:45:50 +0900 Subject: [PATCH 2/3] fix(expect): support arbitrary value equality for `toThrow` and make Error detection robust (#9570) Co-authored-by: Claude Opus 4.5 --- api/expect.md | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/api/expect.md b/api/expect.md index e6d670ee..b0c0a6b7 100644 --- a/api/expect.md +++ b/api/expect.md @@ -779,7 +779,7 @@ test('the number of elements must match exactly', () => { ## toThrowError -- **Type:** `(received: any) => Awaitable` +- **Type:** `(expected?: any) => Awaitable` - **Alias:** `toThrow` @@ -789,7 +789,7 @@ You can provide an optional argument to test that a specific error is thrown: - `RegExp`: error message matches the pattern - `string`: error message includes the substring -- `Error`, `AsymmetricMatcher`: compare with a received object similar to `toEqual(received)` +- any other value: compare with thrown value using deep equality (similar to `toEqual`) :::tip You must wrap the code in a function, otherwise the error will not be caught, and test will fail. @@ -849,6 +849,17 @@ test('throws on pineapples', async () => { ``` ::: +:::tip +You can also test non-Error values that are thrown: + +```ts +test('throws non-Error values', () => { + expect(() => { throw 42 }).toThrowError(42) + expect(() => { throw { message: 'error' } }).toThrowError({ message: 'error' }) +}) +``` +::: + ## toMatchSnapshot - **Type:** `(shape?: Partial | string, hint?: string) => void` From fb4588cf408e7c1b6721f25299939bf34aa07614 Mon Sep 17 00:00:00 2001 From: noise Date: Wed, 4 Feb 2026 10:58:23 +0800 Subject: [PATCH 3/3] docs(cn): dissolve the conflict --- api/expect.md | 16 +++++----------- eslint.config.js | 1 + 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/api/expect.md b/api/expect.md index cef46f8b..1b8b235e 100644 --- a/api/expect.md +++ b/api/expect.md @@ -782,11 +782,7 @@ test('the number of elements must match exactly', () => { ## toThrowError -<<<<<<< HEAD -- **类型:** `(received: any) => Awaitable` -======= -- **Type:** `(expected?: any) => Awaitable` ->>>>>>> 6777bfb4d65ef81facc3a8f09bbe129e90871655 +- **类型:** `(recexpectedeived: any) => Awaitable` - **别名:** `toThrow` @@ -794,15 +790,11 @@ test('the number of elements must match exactly', () => { 我们可以提供一个可选参数来测试是否抛出了特定的错误: -<<<<<<< HEAD - `RegExp`: 错误消息匹配该模式 - `string`: 错误消息包含该子字符串 -- `Error`, `AsymmetricMatcher`: 与接收到的对象进行比较,类似于 `toEqual(received)` -======= -- `RegExp`: error message matches the pattern -- `string`: error message includes the substring - any other value: compare with thrown value using deep equality (similar to `toEqual`) ->>>>>>> 6777bfb4d65ef81facc3a8f09bbe129e90871655 + + :::tip 必须将代码包装在一个函数中,否则错误将无法被捕获,测试将失败。 @@ -863,6 +855,8 @@ test('throws on pineapples', async () => { ::: + + :::tip You can also test non-Error values that are thrown: diff --git a/eslint.config.js b/eslint.config.js index 29738e34..c40eeef0 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -69,6 +69,7 @@ export default antfu( 'no-self-compare': 'off', 'import/no-mutable-exports': 'off', 'no-restricted-globals': 'off', + 'no-throw-literal': 'off', }, }, )