diff --git a/api/expect.md b/api/expect.md index 572b2684..1b8b235e 100644 --- a/api/expect.md +++ b/api/expect.md @@ -782,7 +782,7 @@ test('the number of elements must match exactly', () => { ## toThrowError -- **类型:** `(received: any) => Awaitable` +- **类型:** `(recexpectedeived: any) => Awaitable` - **别名:** `toThrow` @@ -792,7 +792,9 @@ test('the number of elements must match exactly', () => { - `RegExp`: 错误消息匹配该模式 - `string`: 错误消息包含该子字符串 -- `Error`, `AsymmetricMatcher`: 与接收到的对象进行比较,类似于 `toEqual(received)` +- any other value: compare with thrown value using deep equality (similar to `toEqual`) + + :::tip 必须将代码包装在一个函数中,否则错误将无法被捕获,测试将失败。 @@ -853,6 +855,19 @@ 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 - **类型:** `(shape?: Partial | string, hint?: string) => void` diff --git a/api/hooks.md b/api/hooks.md index 2cedf2f0..ae4f8ed1 100644 --- a/api/hooks.md +++ b/api/hooks.md @@ -13,7 +13,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 ``` @@ -55,7 +55,7 @@ beforeEach(async () => { ```ts function afterEach( - body: () => unknown, + body: (context: TestContext) => unknown, timeout?: number, ): void ``` @@ -83,7 +83,7 @@ You can also use [`onTestFinished`](#ontestfinished) during the test execution t ```ts function beforeAll( - body: () => unknown, + body: (context: ModuleContext) => unknown, timeout?: number, ): void ``` @@ -123,7 +123,7 @@ beforeAll(async () => { ```ts function afterAll( - body: () => unknown, + body: (context: ModuleContext) => unknown, timeout?: number, ): void ``` @@ -147,7 +147,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 ``` @@ -254,7 +257,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/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', }, }, ) diff --git a/guide/test-context.md b/guide/test-context.md index 64b72cae..9c1115a3 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -280,14 +280,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: @@ -507,15 +499,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) @@ -833,7 +823,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 @@ -848,3 +838,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 }) => {}) +``` +:::