[READ-ONLY] Mirror of https://github.com/vitest-dev/vitest. Next generation testing framework powered by Vite. vitest.dev
test testing-tools vite
12

Configure Feed

Select the types of activity you want to include in your feed.

feat: implement `aroundEach` and `aroundAll` hooks (#9450)

authored by

Vladimir and committed by
GitHub
(Jan 24, 2026, 11:20 AM +0100) 2a8cb9dc 5d0fd3b6

+4276 -1531
+5
netlify.toml
··· 85 85 to = "/config/browser/webdriverio" 86 86 status = 301 87 87 88 + [[redirects]] 89 + from = "/api/" 90 + to = "/api/test" 91 + status = 301 92 + 88 93 [[headers]] 89 94 for = "/manifest.webmanifest" 90 95
+16 -3
docs/.vitepress/config.ts
··· 154 154 title: 'Vitest', 155 155 items: [ 156 156 { text: 'Guides', link: '/guide/' }, 157 - { text: 'API', link: '/api/' }, 157 + { text: 'API', link: '/api/test' }, 158 158 { text: 'Config', link: '/config/' }, 159 159 ], 160 160 }, ··· 195 195 196 196 nav: [ 197 197 { text: 'Guides', link: '/guide/', activeMatch: '^/guide/' }, 198 - { text: 'API', link: '/api/', activeMatch: '^/api/' }, 198 + { text: 'API', link: '/api/test', activeMatch: '^/api/' }, 199 199 { text: 'Config', link: '/config/', activeMatch: '^/config/' }, 200 200 { 201 201 text: 'Blog', ··· 961 961 '/api': [ 962 962 { 963 963 text: 'Test API Reference', 964 - link: '/api/', 964 + items: [ 965 + { 966 + text: 'Test', 967 + link: '/api/test', 968 + }, 969 + { 970 + text: 'Describe', 971 + link: '/api/describe', 972 + }, 973 + { 974 + text: 'Hooks', 975 + link: '/api/hooks', 976 + }, 977 + ], 965 978 }, 966 979 { 967 980 text: 'Mocks',
+378
docs/api/describe.md
··· 1 + --- 2 + outline: deep 3 + --- 4 + 5 + # describe 6 + 7 + - **Alias:** `suite` 8 + 9 + ```ts 10 + function describe( 11 + name: string | Function, 12 + body?: () => unknown, 13 + timeout?: number 14 + ): void 15 + function describe( 16 + name: string | Function, 17 + options: SuiteOptions, 18 + body?: () => unknown, 19 + ): void 20 + ``` 21 + 22 + `describe` is used to group related tests and benchmarks into a suite. Suites help organize your test files by creating logical blocks, making test output easier to read and enabling shared setup/teardown through [lifecycle hooks](/api/hooks). 23 + 24 + When you use `test` in the top level of file, they are collected as part of the implicit suite for it. Using `describe` you can define a new suite in the current context, as a set of related tests or benchmarks and other nested suites. 25 + 26 + ```ts [basic.spec.ts] 27 + import { describe, expect, test } from 'vitest' 28 + 29 + const person = { 30 + isActive: true, 31 + age: 32, 32 + } 33 + 34 + describe('person', () => { 35 + test('person is defined', () => { 36 + expect(person).toBeDefined() 37 + }) 38 + 39 + test('is active', () => { 40 + expect(person.isActive).toBeTruthy() 41 + }) 42 + 43 + test('age limit', () => { 44 + expect(person.age).toBeLessThanOrEqual(32) 45 + }) 46 + }) 47 + ``` 48 + 49 + You can also nest `describe` blocks if you have a hierarchy of tests: 50 + 51 + ```ts 52 + import { describe, expect, test } from 'vitest' 53 + 54 + function numberToCurrency(value: number | string) { 55 + if (typeof value !== 'number') { 56 + throw new TypeError('Value must be a number') 57 + } 58 + 59 + return value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') 60 + } 61 + 62 + describe('numberToCurrency', () => { 63 + describe('given an invalid number', () => { 64 + test('composed of non-numbers to throw error', () => { 65 + expect(() => numberToCurrency('abc')).toThrowError() 66 + }) 67 + }) 68 + 69 + describe('given a valid number', () => { 70 + test('returns the correct currency format', () => { 71 + expect(numberToCurrency(10000)).toBe('10,000.00') 72 + }) 73 + }) 74 + }) 75 + ``` 76 + 77 + ## Test Options 78 + 79 + You can use [test options](/api/test#test-options) to apply configuration to every test inside a suite, including nested suites. This is useful when you want to set timeouts, retries, or other options for a group of related tests. 80 + 81 + ```ts 82 + import { describe, test } from 'vitest' 83 + 84 + describe('slow tests', { timeout: 10_000 }, () => { 85 + test('test 1', () => { /* ... */ }) 86 + test('test 2', () => { /* ... */ }) 87 + 88 + // nested suites also inherit the timeout 89 + describe('nested', () => { 90 + test('test 3', () => { /* ... */ }) 91 + }) 92 + }) 93 + ``` 94 + 95 + ### `shuffle` 96 + 97 + - **Type:** `boolean` 98 + - **Default:** `false` (configured by [`sequence.shuffle`](/config/sequence#sequence-shuffle)) 99 + - **Alias:** [`describe.shuffle`](#describe-shuffle) 100 + 101 + Run tests within the suite in random order. This option is inherited by nested suites. 102 + 103 + ```ts 104 + import { describe, test } from 'vitest' 105 + 106 + describe('randomized tests', { shuffle: true }, () => { 107 + test('test 1', () => { /* ... */ }) 108 + test('test 2', () => { /* ... */ }) 109 + test('test 3', () => { /* ... */ }) 110 + }) 111 + ``` 112 + 113 + ## describe.skip 114 + 115 + - **Alias:** `suite.skip` 116 + 117 + Use `describe.skip` in a suite to avoid running a particular describe block. 118 + 119 + ```ts 120 + import { assert, describe, test } from 'vitest' 121 + 122 + describe.skip('skipped suite', () => { 123 + test('sqrt', () => { 124 + // Suite skipped, no error 125 + assert.equal(Math.sqrt(4), 3) 126 + }) 127 + }) 128 + ``` 129 + 130 + ## describe.skipIf 131 + 132 + - **Alias:** `suite.skipIf` 133 + 134 + In some cases, you might run suites multiple times with different environments, and some of the suites might be environment-specific. Instead of wrapping the suite with `if`, you can use `describe.skipIf` to skip the suite whenever the condition is truthy. 135 + 136 + ```ts 137 + import { describe, test } from 'vitest' 138 + 139 + const isDev = process.env.NODE_ENV === 'development' 140 + 141 + describe.skipIf(isDev)('prod only test suite', () => { 142 + // this test suite only runs in production 143 + }) 144 + ``` 145 + 146 + ## describe.runIf 147 + 148 + - **Alias:** `suite.runIf` 149 + 150 + Opposite of [describe.skipIf](#describe-skipif). 151 + 152 + ```ts 153 + import { assert, describe, test } from 'vitest' 154 + 155 + const isDev = process.env.NODE_ENV === 'development' 156 + 157 + describe.runIf(isDev)('dev only test suite', () => { 158 + // this test suite only runs in development 159 + }) 160 + ``` 161 + 162 + ## describe.only 163 + 164 + - **Alias:** `suite.only` 165 + 166 + Use `describe.only` to only run certain suites 167 + 168 + ```ts 169 + import { assert, describe, test } from 'vitest' 170 + 171 + // Only this suite (and others marked with only) are run 172 + describe.only('suite', () => { 173 + test('sqrt', () => { 174 + assert.equal(Math.sqrt(4), 3) 175 + }) 176 + }) 177 + 178 + describe('other suite', () => { 179 + // ... will be skipped 180 + }) 181 + ``` 182 + 183 + Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. 184 + 185 + In order to do that, run `vitest` with specific file containing the tests in question: 186 + 187 + ```shell 188 + vitest interesting.test.ts 189 + ``` 190 + 191 + ## describe.concurrent 192 + 193 + - **Alias:** `suite.concurrent` 194 + 195 + `describe.concurrent` runs all inner suites and tests in parallel 196 + 197 + ```ts 198 + import { describe, test } from 'vitest' 199 + 200 + // All suites and tests within this suite will be run in parallel 201 + describe.concurrent('suite', () => { 202 + test('concurrent test 1', async () => { /* ... */ }) 203 + describe('concurrent suite 2', async () => { 204 + test('concurrent test inner 1', async () => { /* ... */ }) 205 + test('concurrent test inner 2', async () => { /* ... */ }) 206 + }) 207 + test.concurrent('concurrent test 3', async () => { /* ... */ }) 208 + }) 209 + ``` 210 + 211 + `.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid: 212 + 213 + ```ts 214 + describe.concurrent(/* ... */) 215 + describe.skip.concurrent(/* ... */) // or describe.concurrent.skip(/* ... */) 216 + describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */) 217 + describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */) 218 + ``` 219 + 220 + When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context) to ensure the right test is detected. 221 + 222 + ```ts 223 + describe.concurrent('suite', () => { 224 + test('concurrent test 1', async ({ expect }) => { 225 + expect(foo).toMatchSnapshot() 226 + }) 227 + test('concurrent test 2', async ({ expect }) => { 228 + expect(foo).toMatchSnapshot() 229 + }) 230 + }) 231 + ``` 232 + 233 + ## describe.sequential 234 + 235 + - **Alias:** `suite.sequential` 236 + 237 + `describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 238 + 239 + ```ts 240 + import { describe, test } from 'vitest' 241 + 242 + describe.concurrent('suite', () => { 243 + test('concurrent test 1', async () => { /* ... */ }) 244 + test('concurrent test 2', async () => { /* ... */ }) 245 + 246 + describe.sequential('', () => { 247 + test('sequential test 1', async () => { /* ... */ }) 248 + test('sequential test 2', async () => { /* ... */ }) 249 + }) 250 + }) 251 + ``` 252 + 253 + ## describe.shuffle 254 + 255 + - **Alias:** `suite.shuffle` 256 + 257 + Vitest provides a way to run all tests in random order via CLI flag [`--sequence.shuffle`](/guide/cli) or config option [`sequence.shuffle`](/config/#sequence-shuffle), but if you want to have only part of your test suite to run tests in random order, you can mark it with this flag. 258 + 259 + ```ts 260 + import { describe, test } from 'vitest' 261 + 262 + // or describe('suite', { shuffle: true }, ...) 263 + describe.shuffle('suite', () => { 264 + test('random test 1', async () => { /* ... */ }) 265 + test('random test 2', async () => { /* ... */ }) 266 + test('random test 3', async () => { /* ... */ }) 267 + 268 + // `shuffle` is inherited 269 + describe('still random', () => { 270 + test('random 4.1', async () => { /* ... */ }) 271 + test('random 4.2', async () => { /* ... */ }) 272 + }) 273 + 274 + // disable shuffle inside 275 + describe('not random', { shuffle: false }, () => { 276 + test('in order 5.1', async () => { /* ... */ }) 277 + test('in order 5.2', async () => { /* ... */ }) 278 + }) 279 + }) 280 + // order depends on sequence.seed option in config (Date.now() by default) 281 + ``` 282 + 283 + `.skip`, `.only`, and `.todo` works with random suites. 284 + 285 + ## describe.todo 286 + 287 + - **Alias:** `suite.todo` 288 + 289 + Use `describe.todo` to stub suites to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. 290 + 291 + ```ts 292 + // An entry will be shown in the report for this suite 293 + describe.todo('unimplemented suite') 294 + ``` 295 + 296 + ## describe.each 297 + 298 + - **Alias:** `suite.each` 299 + 300 + ::: tip 301 + While `describe.each` is provided for Jest compatibility, 302 + Vitest also has [`describe.for`](#describe-for) which simplifies argument types and aligns with [`test.for`](/api/test#test-for). 303 + ::: 304 + 305 + Use `describe.each` if you have more than one test that depends on the same data. 306 + 307 + ```ts 308 + import { describe, expect, test } from 'vitest' 309 + 310 + describe.each([ 311 + { a: 1, b: 1, expected: 2 }, 312 + { a: 1, b: 2, expected: 3 }, 313 + { a: 2, b: 1, expected: 3 }, 314 + ])('describe object add($a, $b)', ({ a, b, expected }) => { 315 + test(`returns ${expected}`, () => { 316 + expect(a + b).toBe(expected) 317 + }) 318 + 319 + test(`returned value not be greater than ${expected}`, () => { 320 + expect(a + b).not.toBeGreaterThan(expected) 321 + }) 322 + 323 + test(`returned value not be less than ${expected}`, () => { 324 + expect(a + b).not.toBeLessThan(expected) 325 + }) 326 + }) 327 + ``` 328 + 329 + * First row should be column names, separated by `|`; 330 + * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. 331 + 332 + ```ts 333 + import { describe, expect, test } from 'vitest' 334 + 335 + describe.each` 336 + a | b | expected 337 + ${1} | ${1} | ${2} 338 + ${'a'} | ${'b'} | ${'ab'} 339 + ${[]} | ${'b'} | ${'b'} 340 + ${{}} | ${'b'} | ${'[object Object]b'} 341 + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 342 + `('describe template string add($a, $b)', ({ a, b, expected }) => { 343 + test(`returns ${expected}`, () => { 344 + expect(a + b).toBe(expected) 345 + }) 346 + }) 347 + ``` 348 + 349 + ## describe.for 350 + 351 + - **Alias:** `suite.for` 352 + 353 + The difference from `describe.each` is how array case is provided in the arguments. 354 + Other non array case (including template string usage) works exactly same. 355 + 356 + ```ts 357 + // `each` spreads array case 358 + describe.each([ 359 + [1, 1, 2], 360 + [1, 2, 3], 361 + [2, 1, 3], 362 + ])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] 363 + test('test', () => { 364 + expect(a + b).toBe(expected) 365 + }) 366 + }) 367 + 368 + // `for` doesn't spread array case 369 + describe.for([ 370 + [1, 1, 2], 371 + [1, 2, 3], 372 + [2, 1, 3], 373 + ])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] 374 + test('test', () => { 375 + expect(a + b).toBe(expected) 376 + }) 377 + }) 378 + ```
+2 -2
docs/api/expect.md
··· 31 31 expect(input).toBe(2) // jest API 32 32 ``` 33 33 34 - Technically this example doesn't use [`test`](/api/#test) function, so in the console you will see Node.js error instead of Vitest output. To learn more about `test`, please read [Test API Reference](/api/). 34 + Technically this example doesn't use [`test`](/api/test) function, so in the console you will see Node.js error instead of Vitest output. To learn more about `test`, please read [Test API Reference](/api/test). 35 35 36 36 Also, `expect` can be used statically to access matcher functions, described later, and more. 37 37 ··· 98 98 ``` 99 99 100 100 ::: warning 101 - `expect.soft` can only be used inside the [`test`](/api/#test) function. 101 + `expect.soft` can only be used inside the [`test`](/api/test) function. 102 102 ::: 103 103 104 104 ## poll
+461
docs/api/hooks.md
··· 1 + --- 2 + outline: deep 3 + --- 4 + 5 + # Hooks 6 + 7 + These functions allow you to hook into the life cycle of tests to avoid repeating setup and teardown code. They apply to the current context: the file if they are used at the top-level or the current suite if they are inside a `describe` block. These hooks are not called, when you are running Vitest as a [type checker](/guide/testing-types). 8 + 9 + Test hooks are called in a stack order ("after" hooks are reversed) by default, but you can configure it via [`sequence.hooks`](/config/sequence#sequence-hooks) option. 10 + 11 + ## beforeEach 12 + 13 + ```ts 14 + function beforeEach( 15 + body: () => unknown, 16 + timeout?: number, 17 + ): void 18 + ``` 19 + 20 + Register a callback to be called before each of the tests in the current suite runs. 21 + If the function returns a promise, Vitest waits until the promise resolve before running the test. 22 + 23 + Optionally, you can pass a timeout (in milliseconds) defining how long to wait before terminating. The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 24 + 25 + ```ts 26 + import { beforeEach } from 'vitest' 27 + 28 + beforeEach(async () => { 29 + // Clear mocks and add some testing data before each test run 30 + await stopMocking() 31 + await addUser({ name: 'John' }) 32 + }) 33 + ``` 34 + 35 + Here, the `beforeEach` ensures that user is added for each test. 36 + 37 + `beforeEach` can also return an optional cleanup function (equivalent to [`afterEach`](#aftereach)): 38 + 39 + ```ts 40 + import { beforeEach } from 'vitest' 41 + 42 + beforeEach(async () => { 43 + // called once before each test run 44 + await prepareSomething() 45 + 46 + // clean up function, called once after each test run 47 + return async () => { 48 + await resetSomething() 49 + } 50 + }) 51 + ``` 52 + 53 + ## afterEach 54 + 55 + ```ts 56 + function afterEach( 57 + body: () => unknown, 58 + timeout?: number, 59 + ): void 60 + ``` 61 + 62 + Register a callback to be called after each one of the tests in the current suite completes. 63 + If the function returns a promise, Vitest waits until the promise resolve before continuing. 64 + 65 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 66 + 67 + ```ts 68 + import { afterEach } from 'vitest' 69 + 70 + afterEach(async () => { 71 + await clearTestingData() // clear testing data after each test run 72 + }) 73 + ``` 74 + 75 + Here, the `afterEach` ensures that testing data is cleared after each test runs. 76 + 77 + ::: tip 78 + You can also use [`onTestFinished`](#ontestfinished) during the test execution to cleanup any state after the test has finished running. 79 + ::: 80 + 81 + ## beforeAll 82 + 83 + ```ts 84 + function beforeAll( 85 + body: () => unknown, 86 + timeout?: number, 87 + ): void 88 + ``` 89 + 90 + Register a callback to be called once before starting to run all tests in the current suite. 91 + If the function returns a promise, Vitest waits until the promise resolve before running tests. 92 + 93 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 94 + 95 + ```ts 96 + import { beforeAll } from 'vitest' 97 + 98 + beforeAll(async () => { 99 + await startMocking() // called once before all tests run 100 + }) 101 + ``` 102 + 103 + Here the `beforeAll` ensures that the mock data is set up before tests run. 104 + 105 + `beforeAll` can also return an optional cleanup function (equivalent to [`afterAll`](#afterall)): 106 + 107 + ```ts 108 + import { beforeAll } from 'vitest' 109 + 110 + beforeAll(async () => { 111 + // called once before all tests run 112 + await startMocking() 113 + 114 + // clean up function, called once after all tests run 115 + return async () => { 116 + await stopMocking() 117 + } 118 + }) 119 + ``` 120 + 121 + ## afterAll 122 + 123 + ```ts 124 + function afterAll( 125 + body: () => unknown, 126 + timeout?: number, 127 + ): void 128 + ``` 129 + 130 + Register a callback to be called once after all tests have run in the current suite. 131 + If the function returns a promise, Vitest waits until the promise resolve before continuing. 132 + 133 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 134 + 135 + ```ts 136 + import { afterAll } from 'vitest' 137 + 138 + afterAll(async () => { 139 + await stopMocking() // this method is called after all tests run 140 + }) 141 + ``` 142 + 143 + Here the `afterAll` ensures that `stopMocking` method is called after all tests run. 144 + 145 + ## aroundEach 146 + 147 + ```ts 148 + function aroundEach( 149 + body: (runTest: () => Promise<void>, context: TestContext) => Promise<void>, 150 + timeout?: number, 151 + ): void 152 + ``` 153 + 154 + Register a callback function that wraps around each test within the current suite. The callback receives a `runTest` function that **must** be called to run the test. 155 + 156 + The `runTest()` function runs `beforeEach` hooks, the test itself, fixtures accessed in the test, and `afterEach` hooks. Fixtures that are accessed in the `aroundEach` callback are initialized before `runTest()` is called and are torn down after the aroundEach teardown code completes, allowing you to safely use them in both setup and teardown phases. 157 + 158 + ::: warning 159 + You **must** call `runTest()` within your callback. If `runTest()` is not called, the test will fail with an error. 160 + ::: 161 + 162 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The timeout applies independently to the setup phase (before `runTest()`) and teardown phase (after `runTest()`). The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 163 + 164 + ```ts 165 + import { aroundEach, test } from 'vitest' 166 + 167 + aroundEach(async (runTest) => { 168 + await db.transaction(runTest) 169 + }) 170 + 171 + test('insert user', async () => { 172 + await db.insert({ name: 'Alice' }) 173 + // transaction is automatically rolled back after the test 174 + }) 175 + ``` 176 + 177 + ::: tip When to use `aroundEach` 178 + Use `aroundEach` when your test needs to run **inside a context** that wraps around it, such as: 179 + - Wrapping tests in [AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage) context 180 + - Wrapping tests with tracing spans 181 + - Database transactions 182 + 183 + If you just need to run code before and after tests, prefer using [`beforeEach`](#beforeeach) with a cleanup return function: 184 + ```ts 185 + beforeEach(async () => { 186 + await database.connect() 187 + return async () => { 188 + await database.disconnect() 189 + } 190 + }) 191 + ``` 192 + ::: 193 + 194 + ### Multiple Hooks 195 + 196 + When multiple `aroundEach` hooks are registered, they are nested inside each other. The first registered hook is the outermost wrapper: 197 + 198 + ```ts 199 + aroundEach(async (runTest) => { 200 + console.log('outer before') 201 + await runTest() 202 + console.log('outer after') 203 + }) 204 + 205 + aroundEach(async (runTest) => { 206 + console.log('inner before') 207 + await runTest() 208 + console.log('inner after') 209 + }) 210 + 211 + // Output order: 212 + // outer before 213 + // inner before 214 + // test 215 + // inner after 216 + // outer after 217 + ``` 218 + 219 + ### Context and Fixtures 220 + 221 + The callback receives the test context as the second argument which means that you can use fixtures with `aroundEach`: 222 + 223 + ```ts 224 + import { aroundEach, test as base } from 'vitest' 225 + 226 + const test = base.extend<{ db: Database; user: User }>({ 227 + db: async ({}, use) => { 228 + // db is created before `aroundEach` hook 229 + const db = await createTestDatabase() 230 + await use(db) 231 + await db.close() 232 + }, 233 + user: async ({ db }, use) => { 234 + // `user` runs as part of the transaction 235 + // because it's accessed inside the `test` 236 + const user = await db.createUser() 237 + await use(user) 238 + }, 239 + }) 240 + 241 + // note that `aroundEach` is available on test 242 + // for a better TypeScript support of fixtures 243 + test.aroundEach(async (runTest, { db }) => { 244 + await db.transaction(runTest) 245 + }) 246 + 247 + test('insert user', async ({ db, user }) => { 248 + await db.insert(user) 249 + }) 250 + ``` 251 + 252 + ## aroundAll 253 + 254 + ```ts 255 + function aroundAll( 256 + body: (runSuite: () => Promise<void>) => Promise<void>, 257 + timeout?: number, 258 + ): void 259 + ``` 260 + 261 + Register a callback function that wraps around all tests within the current suite. The callback receives a `runSuite` function that **must** be called to run the suite's tests. 262 + 263 + The `runSuite()` function runs all tests in the suite, including `beforeAll`/`afterAll`/`beforeEach`/`afterEach` hooks, `aroundEach` hooks, and fixtures. 264 + 265 + ::: warning 266 + You **must** call `runSuite()` within your callback. If `runSuite()` is not called, the hook will fail with an error and all tests in the suite will be skipped. 267 + ::: 268 + 269 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The timeout applies independently to the setup phase (before `runSuite()`) and teardown phase (after `runSuite()`). The default is 10 seconds, and can be configured globally with [`hookTimeout`](/config/hooktimeout). 270 + 271 + ```ts 272 + import { aroundAll, test } from 'vitest' 273 + 274 + aroundAll(async (runSuite) => { 275 + await tracer.trace('test-suite', runSuite) 276 + }) 277 + 278 + test('test 1', () => { 279 + // Runs within the tracing span 280 + }) 281 + 282 + test('test 2', () => { 283 + // Also runs within the same tracing span 284 + }) 285 + ``` 286 + 287 + ::: tip When to use `aroundAll` 288 + Use `aroundAll` when your suite needs to run **inside a context** that wraps around all tests, such as: 289 + - Wrapping an entire suite in [AsyncLocalStorage](https://nodejs.org/api/async_context.html#class-asynclocalstorage) context 290 + - Wrapping a suite with tracing spans 291 + - Database transactions 292 + 293 + If you just need to run code once before and after all tests, prefer using [`beforeAll`](#beforeall) with a cleanup return function: 294 + ```ts 295 + beforeAll(async () => { 296 + await server.start() 297 + return async () => { 298 + await server.stop() 299 + } 300 + }) 301 + ``` 302 + ::: 303 + 304 + ### Multiple Hooks 305 + 306 + When multiple `aroundAll` hooks are registered, they are nested inside each other. The first registered hook is the outermost wrapper: 307 + 308 + ```ts 309 + aroundAll(async (runSuite) => { 310 + console.log('outer before') 311 + await runSuite() 312 + console.log('outer after') 313 + }) 314 + 315 + aroundAll(async (runSuite) => { 316 + console.log('inner before') 317 + await runSuite() 318 + console.log('inner after') 319 + }) 320 + 321 + // Output order: outer before → inner before → tests → inner after → outer after 322 + ``` 323 + 324 + Each suite has its own independent `aroundAll` hooks. Parent suite's `aroundAll` wraps around child suite's execution: 325 + 326 + ```ts 327 + import { AsyncLocalStorage } from 'node:async_hooks' 328 + import { aroundAll, describe, test } from 'vitest' 329 + 330 + const context = new AsyncLocalStorage<{ suiteId: string }>() 331 + 332 + aroundAll(async (runSuite) => { 333 + await context.run({ suiteId: 'root' }, runSuite) 334 + }) 335 + 336 + test('root test', () => { 337 + // context.getStore() returns { suiteId: 'root' } 338 + }) 339 + 340 + describe('nested', () => { 341 + aroundAll(async (runSuite) => { 342 + // Parent's context is available here 343 + await context.run({ suiteId: 'nested' }, runSuite) 344 + }) 345 + 346 + test('nested test', () => { 347 + // context.getStore() returns { suiteId: 'nested' } 348 + }) 349 + }) 350 + ``` 351 + 352 + ## Test Hooks 353 + 354 + Vitest provides a few hooks that you can call _during_ the test execution to cleanup the state when the test has finished running. 355 + 356 + ::: warning 357 + These hooks will throw an error if they are called outside of the test body. 358 + ::: 359 + 360 + ### onTestFinished {#ontestfinished} 361 + 362 + This hook is always called after the test has finished running. It is called after `afterEach` hooks since they can influence the test result. It receives an `TestContext` object like `beforeEach` and `afterEach`. 363 + 364 + ```ts {1,5} 365 + import { onTestFinished, test } from 'vitest' 366 + 367 + test('performs a query', () => { 368 + const db = connectDb() 369 + onTestFinished(() => db.close()) 370 + db.query('SELECT * FROM users') 371 + }) 372 + ``` 373 + 374 + ::: warning 375 + If you are running tests concurrently, you should always use `onTestFinished` hook from the test context since Vitest doesn't track concurrent tests in global hooks: 376 + 377 + ```ts {3,5} 378 + import { test } from 'vitest' 379 + 380 + test.concurrent('performs a query', ({ onTestFinished }) => { 381 + const db = connectDb() 382 + onTestFinished(() => db.close()) 383 + db.query('SELECT * FROM users') 384 + }) 385 + ``` 386 + ::: 387 + 388 + This hook is particularly useful when creating reusable logic: 389 + 390 + ```ts 391 + // this can be in a separate file 392 + function getTestDb() { 393 + const db = connectMockedDb() 394 + onTestFinished(() => db.close()) 395 + return db 396 + } 397 + 398 + test('performs a user query', async () => { 399 + const db = getTestDb() 400 + expect( 401 + await db.query('SELECT * from users').perform() 402 + ).toEqual([]) 403 + }) 404 + 405 + test('performs an organization query', async () => { 406 + const db = getTestDb() 407 + expect( 408 + await db.query('SELECT * from organizations').perform() 409 + ).toEqual([]) 410 + }) 411 + ``` 412 + 413 + It is also a good practice to cleanup your spies after each test, so they don't leak into other tests. You can do so by enabling [`restoreMocks`](/config/restoremocks) config globally, or restoring the spy inside `onTestFinished` (if you try to restore the mock at the end of the test, it won't be restored if one of the assertions fails - using `onTestFinished` ensures the code always runs): 414 + 415 + ```ts 416 + import { onTestFinished, test } from 'vitest' 417 + 418 + test('performs a query', () => { 419 + const spy = vi.spyOn(db, 'query') 420 + onTestFinished(() => spy.mockClear()) 421 + 422 + db.query('SELECT * FROM users') 423 + expect(spy).toHaveBeenCalled() 424 + }) 425 + ``` 426 + 427 + ::: tip 428 + This hook is always called in reverse order and is not affected by [`sequence.hooks`](/config/#sequence-hooks) option. 429 + ::: 430 + 431 + ### onTestFailed 432 + 433 + This hook is called only after the test has failed. It is called after `afterEach` hooks since they can influence the test result. It receives a `TestContext` object like `beforeEach` and `afterEach`. This hook is useful for debugging. 434 + 435 + ```ts {1,5-7} 436 + import { onTestFailed, test } from 'vitest' 437 + 438 + test('performs a query', () => { 439 + const db = connectDb() 440 + onTestFailed(({ task }) => { 441 + console.log(task.result.errors) 442 + }) 443 + db.query('SELECT * FROM users') 444 + }) 445 + ``` 446 + 447 + ::: warning 448 + If you are running tests concurrently, you should always use `onTestFailed` hook from the test context since Vitest doesn't track concurrent tests in global hooks: 449 + 450 + ```ts {3,5-7} 451 + import { test } from 'vitest' 452 + 453 + test.concurrent('performs a query', ({ onTestFailed }) => { 454 + const db = connectDb() 455 + onTestFailed(({ task }) => { 456 + console.log(task.result.errors) 457 + }) 458 + db.query('SELECT * FROM users') 459 + }) 460 + ``` 461 + :::
-1316
docs/api/index.md
··· 1 - --- 2 - outline: deep 3 - --- 4 - 5 - # Test API Reference 6 - 7 - The following types are used in the type signatures below 8 - 9 - ```ts 10 - type Awaitable<T> = T | PromiseLike<T> 11 - type TestFunction = () => Awaitable<void> 12 - 13 - interface TestOptions { 14 - /** 15 - * Will fail the test if it takes too long to execute 16 - */ 17 - timeout?: number 18 - /** 19 - * Will retry the test specific number of times if it fails 20 - * 21 - * @default 0 22 - */ 23 - retry?: number 24 - /** 25 - * Will repeat the same test several times even if it fails each time 26 - * If you have "retry" option and it fails, it will use every retry in each cycle 27 - * Useful for debugging random failings 28 - * 29 - * @default 0 30 - */ 31 - repeats?: number 32 - /** 33 - * Custom tags of the test. Useful for filtering tests. 34 - */ 35 - tags?: string[] | string 36 - } 37 - ``` 38 - 39 - <!-- TODO: rewrite this into separate test files with options highlighted --> 40 - 41 - When a test function returns a promise, the runner will wait until it is resolved to collect async expectations. If the promise is rejected, the test will fail. 42 - 43 - ::: tip 44 - In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If this form is used, the test will not be concluded until `done` is called. You can achieve the same using an `async` function, see the [Migration guide Done Callback section](/guide/migration#done-callback). 45 - ::: 46 - 47 - You can define options by chaining properties on a function: 48 - 49 - ```ts 50 - import { test } from 'vitest' 51 - 52 - test.skip('skipped test', () => { 53 - // some logic that fails right now 54 - }) 55 - 56 - test.concurrent.skip('skipped concurrent test', () => { 57 - // some logic that fails right now 58 - }) 59 - ``` 60 - 61 - But you can also provide an object as a second argument instead: 62 - 63 - ```ts 64 - import { test } from 'vitest' 65 - 66 - test('skipped test', { skip: true }, () => { 67 - // some logic that fails right now 68 - }) 69 - 70 - test('skipped concurrent test', { skip: true, concurrent: true }, () => { 71 - // some logic that fails right now 72 - }) 73 - ``` 74 - 75 - They both work in exactly the same way. To use either one is purely a stylistic choice. 76 - 77 - Note that if you are providing timeout as the last argument, you cannot use options anymore: 78 - 79 - ```ts 80 - import { test } from 'vitest' 81 - 82 - // ✅ this works 83 - test.skip('heavy test', () => { 84 - // ... 85 - }, 10_000) 86 - 87 - // ❌ this doesn't work 88 - test('heavy test', { skip: true }, () => { 89 - // ... 90 - }, 10_000) 91 - ``` 92 - 93 - However, you can provide a timeout inside the object: 94 - 95 - ```ts 96 - import { test } from 'vitest' 97 - 98 - // ✅ this works 99 - test('heavy test', { skip: true, timeout: 10_000 }, () => { 100 - // ... 101 - }) 102 - ``` 103 - 104 - ## test 105 - 106 - - **Alias:** `it` 107 - 108 - `test` defines a set of related expectations. It receives the test name and a function that holds the expectations to test. 109 - 110 - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout) 111 - 112 - ```ts 113 - import { expect, test } from 'vitest' 114 - 115 - test('should work as expected', () => { 116 - expect(Math.sqrt(4)).toBe(2) 117 - }) 118 - ``` 119 - 120 - ### test.extend {#test-extended} 121 - 122 - - **Alias:** `it.extend` 123 - 124 - Use `test.extend` to extend the test context with custom fixtures. This will return a new `test` and it's also extendable, so you can compose more fixtures or override existing ones by extending it as you need. See [Extend Test Context](/guide/test-context.html#test-extend) for more information. 125 - 126 - ```ts 127 - import { expect, test } from 'vitest' 128 - 129 - const todos = [] 130 - const archive = [] 131 - 132 - const myTest = test.extend({ 133 - todos: async ({ task }, use) => { 134 - todos.push(1, 2, 3) 135 - await use(todos) 136 - todos.length = 0 137 - }, 138 - archive 139 - }) 140 - 141 - myTest('add item', ({ todos }) => { 142 - expect(todos.length).toBe(3) 143 - 144 - todos.push(4) 145 - expect(todos.length).toBe(4) 146 - }) 147 - ``` 148 - 149 - ### test.skip 150 - 151 - - **Alias:** `it.skip` 152 - 153 - If you want to skip running certain tests, but you don't want to delete the code due to any reason, you can use `test.skip` to avoid running them. 154 - 155 - ```ts 156 - import { assert, test } from 'vitest' 157 - 158 - test.skip('skipped test', () => { 159 - // Test skipped, no error 160 - assert.equal(Math.sqrt(4), 3) 161 - }) 162 - ``` 163 - 164 - You can also skip test by calling `skip` on its [context](/guide/test-context) dynamically: 165 - 166 - ```ts 167 - import { assert, test } from 'vitest' 168 - 169 - test('skipped test', (context) => { 170 - context.skip() 171 - // Test skipped, no error 172 - assert.equal(Math.sqrt(4), 3) 173 - }) 174 - ``` 175 - 176 - Since Vitest 3.1, if the condition is unknown, you can provide it to the `skip` method as the first arguments: 177 - 178 - ```ts 179 - import { assert, test } from 'vitest' 180 - 181 - test('skipped test', (context) => { 182 - context.skip(Math.random() < 0.5, 'optional message') 183 - // Test skipped, no error 184 - assert.equal(Math.sqrt(4), 3) 185 - }) 186 - ``` 187 - 188 - ### test.skipIf 189 - 190 - - **Alias:** `it.skipIf` 191 - 192 - In some cases you might run tests multiple times with different environments, and some of the tests might be environment-specific. Instead of wrapping the test code with `if`, you can use `test.skipIf` to skip the test whenever the condition is truthy. 193 - 194 - ```ts 195 - import { assert, test } from 'vitest' 196 - 197 - const isDev = process.env.NODE_ENV === 'development' 198 - 199 - test.skipIf(isDev)('prod only test', () => { 200 - // this test only runs in production 201 - }) 202 - ``` 203 - 204 - ::: warning 205 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 206 - ::: 207 - 208 - ### test.runIf 209 - 210 - - **Alias:** `it.runIf` 211 - 212 - Opposite of [test.skipIf](#test-skipif). 213 - 214 - ```ts 215 - import { assert, test } from 'vitest' 216 - 217 - const isDev = process.env.NODE_ENV === 'development' 218 - 219 - test.runIf(isDev)('dev only test', () => { 220 - // this test only runs in development 221 - }) 222 - ``` 223 - 224 - ::: warning 225 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 226 - ::: 227 - 228 - ### test.only 229 - 230 - - **Alias:** `it.only` 231 - 232 - Use `test.only` to only run certain tests in a given suite. This is useful when debugging. 233 - 234 - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds, and can be configured globally with [testTimeout](/config/#testtimeout). 235 - 236 - ```ts 237 - import { assert, test } from 'vitest' 238 - 239 - test.only('test', () => { 240 - // Only this test (and others marked with only) are run 241 - assert.equal(Math.sqrt(4), 2) 242 - }) 243 - ``` 244 - 245 - Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. 246 - 247 - In order to do that run `vitest` with specific file containing the tests in question. 248 - ``` 249 - # vitest interesting.test.ts 250 - ``` 251 - 252 - ### test.concurrent 253 - 254 - - **Alias:** `it.concurrent` 255 - 256 - `test.concurrent` marks consecutive tests to be run in parallel. It receives the test name, an async function with the tests to collect, and an optional timeout (in milliseconds). 257 - 258 - ```ts 259 - import { describe, test } from 'vitest' 260 - 261 - // The two tests marked with concurrent will be run in parallel 262 - describe('suite', () => { 263 - test('serial test', async () => { /* ... */ }) 264 - test.concurrent('concurrent test 1', async () => { /* ... */ }) 265 - test.concurrent('concurrent test 2', async () => { /* ... */ }) 266 - }) 267 - ``` 268 - 269 - `test.skip`, `test.only`, and `test.todo` works with concurrent tests. All the following combinations are valid: 270 - 271 - ```ts 272 - test.concurrent(/* ... */) 273 - test.skip.concurrent(/* ... */) // or test.concurrent.skip(/* ... */) 274 - test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) 275 - test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) 276 - ``` 277 - 278 - When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context.md) to ensure the right test is detected. 279 - 280 - ```ts 281 - test.concurrent('test 1', async ({ expect }) => { 282 - expect(foo).toMatchSnapshot() 283 - }) 284 - test.concurrent('test 2', async ({ expect }) => { 285 - expect(foo).toMatchSnapshot() 286 - }) 287 - ``` 288 - 289 - ::: warning 290 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 291 - ::: 292 - 293 - ### test.sequential 294 - 295 - - **Alias:** `it.sequential` 296 - 297 - `test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 298 - 299 - ```ts 300 - import { describe, test } from 'vitest' 301 - 302 - // with config option { sequence: { concurrent: true } } 303 - test('concurrent test 1', async () => { /* ... */ }) 304 - test('concurrent test 2', async () => { /* ... */ }) 305 - 306 - test.sequential('sequential test 1', async () => { /* ... */ }) 307 - test.sequential('sequential test 2', async () => { /* ... */ }) 308 - 309 - // within concurrent suite 310 - describe.concurrent('suite', () => { 311 - test('concurrent test 1', async () => { /* ... */ }) 312 - test('concurrent test 2', async () => { /* ... */ }) 313 - 314 - test.sequential('sequential test 1', async () => { /* ... */ }) 315 - test.sequential('sequential test 2', async () => { /* ... */ }) 316 - }) 317 - ``` 318 - 319 - ### test.todo 320 - 321 - - **Alias:** `it.todo` 322 - 323 - Use `test.todo` to stub tests to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. 324 - 325 - ```ts 326 - // An entry will be shown in the report for this test 327 - test.todo('unimplemented test') 328 - ``` 329 - 330 - ### test.fails 331 - 332 - - **Alias:** `it.fails` 333 - 334 - Use `test.fails` to indicate that an assertion will fail explicitly. 335 - 336 - ```ts 337 - import { expect, test } from 'vitest' 338 - 339 - function myAsyncFunc() { 340 - return new Promise(resolve => resolve(1)) 341 - } 342 - test.fails('fail test', async () => { 343 - await expect(myAsyncFunc()).rejects.toBe(1) 344 - }) 345 - ``` 346 - 347 - ::: warning 348 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 349 - ::: 350 - 351 - ### test.each 352 - 353 - - **Alias:** `it.each` 354 - 355 - ::: tip 356 - While `test.each` is provided for Jest compatibility, 357 - Vitest also has [`test.for`](#test-for) with an additional feature to integrate [`TestContext`](/guide/test-context). 358 - ::: 359 - 360 - Use `test.each` when you need to run the same test with different variables. 361 - You can inject parameters with [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) in the test name in the order of the test function parameters. 362 - 363 - - `%s`: string 364 - - `%d`: number 365 - - `%i`: integer 366 - - `%f`: floating point value 367 - - `%j`: json 368 - - `%o`: object 369 - - `%#`: 0-based index of the test case 370 - - `%$`: 1-based index of the test case 371 - - `%%`: single percent sign ('%') 372 - 373 - ```ts 374 - import { expect, test } from 'vitest' 375 - 376 - test.each([ 377 - [1, 1, 2], 378 - [1, 2, 3], 379 - [2, 1, 3], 380 - ])('add(%i, %i) -> %i', (a, b, expected) => { 381 - expect(a + b).toBe(expected) 382 - }) 383 - 384 - // this will return 385 - // ✓ add(1, 1) -> 2 386 - // ✓ add(1, 2) -> 3 387 - // ✓ add(2, 1) -> 3 388 - ``` 389 - 390 - You can also access object properties and array elements with `$` prefix: 391 - 392 - ```ts 393 - test.each([ 394 - { a: 1, b: 1, expected: 2 }, 395 - { a: 1, b: 2, expected: 3 }, 396 - { a: 2, b: 1, expected: 3 }, 397 - ])('add($a, $b) -> $expected', ({ a, b, expected }) => { 398 - expect(a + b).toBe(expected) 399 - }) 400 - 401 - // this will return 402 - // ✓ add(1, 1) -> 2 403 - // ✓ add(1, 2) -> 3 404 - // ✓ add(2, 1) -> 3 405 - 406 - test.each([ 407 - [1, 1, 2], 408 - [1, 2, 3], 409 - [2, 1, 3], 410 - ])('add($0, $1) -> $2', (a, b, expected) => { 411 - expect(a + b).toBe(expected) 412 - }) 413 - 414 - // this will return 415 - // ✓ add(1, 1) -> 2 416 - // ✓ add(1, 2) -> 3 417 - // ✓ add(2, 1) -> 3 418 - ``` 419 - 420 - You can also access Object attributes with `.`, if you are using objects as arguments: 421 - 422 - ```ts 423 - test.each` 424 - a | b | expected 425 - ${{ val: 1 }} | ${'b'} | ${'1b'} 426 - ${{ val: 2 }} | ${'b'} | ${'2b'} 427 - ${{ val: 3 }} | ${'b'} | ${'3b'} 428 - `('add($a.val, $b) -> $expected', ({ a, b, expected }) => { 429 - expect(a.val + b).toBe(expected) 430 - }) 431 - 432 - // this will return 433 - // ✓ add(1, b) -> 1b 434 - // ✓ add(2, b) -> 2b 435 - // ✓ add(3, b) -> 3b 436 - ``` 437 - 438 - * First row should be column names, separated by `|`; 439 - * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. 440 - 441 - ```ts 442 - import { expect, test } from 'vitest' 443 - 444 - test.each` 445 - a | b | expected 446 - ${1} | ${1} | ${2} 447 - ${'a'} | ${'b'} | ${'ab'} 448 - ${[]} | ${'b'} | ${'b'} 449 - ${{}} | ${'b'} | ${'[object Object]b'} 450 - ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 451 - `('returns $expected when $a is added $b', ({ a, b, expected }) => { 452 - expect(a + b).toBe(expected) 453 - }) 454 - ``` 455 - 456 - ::: tip 457 - Vitest processes `$values` with Chai `format` method. If the value is too truncated, you can increase [chaiConfig.truncateThreshold](/config/#chaiconfig-truncatethreshold) in your config file. 458 - ::: 459 - 460 - ::: warning 461 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 462 - ::: 463 - 464 - ### test.for 465 - 466 - - **Alias:** `it.for` 467 - 468 - Alternative to `test.each` to provide [`TestContext`](/guide/test-context). 469 - 470 - The difference from `test.each` lies in how arrays are provided in the arguments. 471 - Non-array arguments to `test.for` (including template string usage) work exactly the same as for `test.each`. 472 - 473 - ```ts 474 - // `each` spreads arrays 475 - test.each([ 476 - [1, 1, 2], 477 - [1, 2, 3], 478 - [2, 1, 3], 479 - ])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] 480 - expect(a + b).toBe(expected) 481 - }) 482 - 483 - // `for` doesn't spread arrays (notice the square brackets around the arguments) 484 - test.for([ 485 - [1, 1, 2], 486 - [1, 2, 3], 487 - [2, 1, 3], 488 - ])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] 489 - expect(a + b).toBe(expected) 490 - }) 491 - ``` 492 - 493 - The 2nd argument is [`TestContext`](/guide/test-context) and can be used for concurrent snapshots, for example: 494 - 495 - ```ts 496 - test.concurrent.for([ 497 - [1, 1], 498 - [1, 2], 499 - [2, 1], 500 - ])('add(%i, %i)', ([a, b], { expect }) => { 501 - expect(a + b).matchSnapshot() 502 - }) 503 - ``` 504 - 505 - ## bench 506 - 507 - - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 508 - 509 - `bench` defines a benchmark. In Vitest terms, benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results. 510 - 511 - Vitest uses the [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. 512 - 513 - ```ts 514 - import { bench } from 'vitest' 515 - 516 - bench('normal sorting', () => { 517 - const x = [1, 5, 4, 2, 3] 518 - x.sort((a, b) => { 519 - return a - b 520 - }) 521 - }, { time: 1000 }) 522 - ``` 523 - 524 - ```ts 525 - export interface Options { 526 - /** 527 - * time needed for running a benchmark task (milliseconds) 528 - * @default 500 529 - */ 530 - time?: number 531 - 532 - /** 533 - * number of times that a task should run if even the time option is finished 534 - * @default 10 535 - */ 536 - iterations?: number 537 - 538 - /** 539 - * function to get the current timestamp in milliseconds 540 - */ 541 - now?: () => number 542 - 543 - /** 544 - * An AbortSignal for aborting the benchmark 545 - */ 546 - signal?: AbortSignal 547 - 548 - /** 549 - * Throw if a task fails (events will not work if true) 550 - */ 551 - throws?: boolean 552 - 553 - /** 554 - * warmup time (milliseconds) 555 - * @default 100ms 556 - */ 557 - warmupTime?: number 558 - 559 - /** 560 - * warmup iterations 561 - * @default 5 562 - */ 563 - warmupIterations?: number 564 - 565 - /** 566 - * setup function to run before each benchmark task (cycle) 567 - */ 568 - setup?: Hook 569 - 570 - /** 571 - * teardown function to run after each benchmark task (cycle) 572 - */ 573 - teardown?: Hook 574 - } 575 - ``` 576 - After the test case is run, the output structure information is as follows: 577 - 578 - ``` 579 - name hz min max mean p75 p99 p995 p999 rme samples 580 - · normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638 581 - ``` 582 - ```ts 583 - export interface TaskResult { 584 - /* 585 - * the last error that was thrown while running the task 586 - */ 587 - error?: unknown 588 - 589 - /** 590 - * The amount of time in milliseconds to run the benchmark task (cycle). 591 - */ 592 - totalTime: number 593 - 594 - /** 595 - * the minimum value in the samples 596 - */ 597 - min: number 598 - /** 599 - * the maximum value in the samples 600 - */ 601 - max: number 602 - 603 - /** 604 - * the number of operations per second 605 - */ 606 - hz: number 607 - 608 - /** 609 - * how long each operation takes (ms) 610 - */ 611 - period: number 612 - 613 - /** 614 - * task samples of each task iteration time (ms) 615 - */ 616 - samples: number[] 617 - 618 - /** 619 - * samples mean/average (estimate of the population mean) 620 - */ 621 - mean: number 622 - 623 - /** 624 - * samples variance (estimate of the population variance) 625 - */ 626 - variance: number 627 - 628 - /** 629 - * samples standard deviation (estimate of the population standard deviation) 630 - */ 631 - sd: number 632 - 633 - /** 634 - * standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) 635 - */ 636 - sem: number 637 - 638 - /** 639 - * degrees of freedom 640 - */ 641 - df: number 642 - 643 - /** 644 - * critical value of the samples 645 - */ 646 - critical: number 647 - 648 - /** 649 - * margin of error 650 - */ 651 - moe: number 652 - 653 - /** 654 - * relative margin of error 655 - */ 656 - rme: number 657 - 658 - /** 659 - * median absolute deviation 660 - */ 661 - mad: number 662 - 663 - /** 664 - * p50/median percentile 665 - */ 666 - p50: number 667 - 668 - /** 669 - * p75 percentile 670 - */ 671 - p75: number 672 - 673 - /** 674 - * p99 percentile 675 - */ 676 - p99: number 677 - 678 - /** 679 - * p995 percentile 680 - */ 681 - p995: number 682 - 683 - /** 684 - * p999 percentile 685 - */ 686 - p999: number 687 - } 688 - ``` 689 - 690 - ### bench.skip 691 - 692 - - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 693 - 694 - You can use `bench.skip` syntax to skip running certain benchmarks. 695 - 696 - ```ts 697 - import { bench } from 'vitest' 698 - 699 - bench.skip('normal sorting', () => { 700 - const x = [1, 5, 4, 2, 3] 701 - x.sort((a, b) => { 702 - return a - b 703 - }) 704 - }) 705 - ``` 706 - 707 - ### bench.only 708 - 709 - - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 710 - 711 - Use `bench.only` to only run certain benchmarks in a given suite. This is useful when debugging. 712 - 713 - ```ts 714 - import { bench } from 'vitest' 715 - 716 - bench.only('normal sorting', () => { 717 - const x = [1, 5, 4, 2, 3] 718 - x.sort((a, b) => { 719 - return a - b 720 - }) 721 - }) 722 - ``` 723 - 724 - ### bench.todo 725 - 726 - - **Type:** `(name: string | Function) => void` 727 - 728 - Use `bench.todo` to stub benchmarks to be implemented later. 729 - 730 - ```ts 731 - import { bench } from 'vitest' 732 - 733 - bench.todo('unimplemented test') 734 - ``` 735 - 736 - ## describe 737 - 738 - When you use `test` or `bench` in the top level of file, they are collected as part of the implicit suite for it. Using `describe` you can define a new suite in the current context, as a set of related tests or benchmarks and other nested suites. A suite lets you organize your tests and benchmarks so reports are more clear. 739 - 740 - ```ts 741 - // basic.spec.ts 742 - // organizing tests 743 - 744 - import { describe, expect, test } from 'vitest' 745 - 746 - const person = { 747 - isActive: true, 748 - age: 32, 749 - } 750 - 751 - describe('person', () => { 752 - test('person is defined', () => { 753 - expect(person).toBeDefined() 754 - }) 755 - 756 - test('is active', () => { 757 - expect(person.isActive).toBeTruthy() 758 - }) 759 - 760 - test('age limit', () => { 761 - expect(person.age).toBeLessThanOrEqual(32) 762 - }) 763 - }) 764 - ``` 765 - 766 - ```ts 767 - // basic.bench.ts 768 - // organizing benchmarks 769 - 770 - import { bench, describe } from 'vitest' 771 - 772 - describe('sort', () => { 773 - bench('normal', () => { 774 - const x = [1, 5, 4, 2, 3] 775 - x.sort((a, b) => { 776 - return a - b 777 - }) 778 - }) 779 - 780 - bench('reverse', () => { 781 - const x = [1, 5, 4, 2, 3] 782 - x.reverse().sort((a, b) => { 783 - return a - b 784 - }) 785 - }) 786 - }) 787 - ``` 788 - 789 - You can also nest describe blocks if you have a hierarchy of tests or benchmarks: 790 - 791 - ```ts 792 - import { describe, expect, test } from 'vitest' 793 - 794 - function numberToCurrency(value: number | string) { 795 - if (typeof value !== 'number') { 796 - throw new TypeError('Value must be a number') 797 - } 798 - 799 - return value.toFixed(2).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',') 800 - } 801 - 802 - describe('numberToCurrency', () => { 803 - describe('given an invalid number', () => { 804 - test('composed of non-numbers to throw error', () => { 805 - expect(() => numberToCurrency('abc')).toThrowError() 806 - }) 807 - }) 808 - 809 - describe('given a valid number', () => { 810 - test('returns the correct currency format', () => { 811 - expect(numberToCurrency(10000)).toBe('10,000.00') 812 - }) 813 - }) 814 - }) 815 - ``` 816 - 817 - ### describe.skip 818 - 819 - - **Alias:** `suite.skip` 820 - 821 - Use `describe.skip` in a suite to avoid running a particular describe block. 822 - 823 - ```ts 824 - import { assert, describe, test } from 'vitest' 825 - 826 - describe.skip('skipped suite', () => { 827 - test('sqrt', () => { 828 - // Suite skipped, no error 829 - assert.equal(Math.sqrt(4), 3) 830 - }) 831 - }) 832 - ``` 833 - 834 - ### describe.skipIf 835 - 836 - - **Alias:** `suite.skipIf` 837 - 838 - In some cases, you might run suites multiple times with different environments, and some of the suites might be environment-specific. Instead of wrapping the suite with `if`, you can use `describe.skipIf` to skip the suite whenever the condition is truthy. 839 - 840 - ```ts 841 - import { describe, test } from 'vitest' 842 - 843 - const isDev = process.env.NODE_ENV === 'development' 844 - 845 - describe.skipIf(isDev)('prod only test suite', () => { 846 - // this test suite only runs in production 847 - }) 848 - ``` 849 - 850 - ::: warning 851 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 852 - ::: 853 - 854 - ### describe.runIf 855 - 856 - - **Alias:** `suite.runIf` 857 - 858 - Opposite of [describe.skipIf](#describe-skipif). 859 - 860 - ```ts 861 - import { assert, describe, test } from 'vitest' 862 - 863 - const isDev = process.env.NODE_ENV === 'development' 864 - 865 - describe.runIf(isDev)('dev only test suite', () => { 866 - // this test suite only runs in development 867 - }) 868 - ``` 869 - 870 - ::: warning 871 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 872 - ::: 873 - 874 - ### describe.only 875 - 876 - - **Type:** `(name: string | Function, fn: TestFunction, options?: number | TestOptions) => void` 877 - 878 - Use `describe.only` to only run certain suites 879 - 880 - ```ts 881 - import { assert, describe, test } from 'vitest' 882 - 883 - // Only this suite (and others marked with only) are run 884 - describe.only('suite', () => { 885 - test('sqrt', () => { 886 - assert.equal(Math.sqrt(4), 3) 887 - }) 888 - }) 889 - 890 - describe('other suite', () => { 891 - // ... will be skipped 892 - }) 893 - ``` 894 - 895 - Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. 896 - 897 - In order to do that run `vitest` with specific file containing the tests in question. 898 - ``` 899 - # vitest interesting.test.ts 900 - ``` 901 - 902 - ### describe.concurrent 903 - 904 - - **Alias:** `suite.concurrent` 905 - 906 - `describe.concurrent` runs all inner suites and tests in parallel 907 - 908 - ```ts 909 - import { describe, test } from 'vitest' 910 - 911 - // All suites and tests within this suite will be run in parallel 912 - describe.concurrent('suite', () => { 913 - test('concurrent test 1', async () => { /* ... */ }) 914 - describe('concurrent suite 2', async () => { 915 - test('concurrent test inner 1', async () => { /* ... */ }) 916 - test('concurrent test inner 2', async () => { /* ... */ }) 917 - }) 918 - test.concurrent('concurrent test 3', async () => { /* ... */ }) 919 - }) 920 - ``` 921 - 922 - `.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid: 923 - 924 - ```ts 925 - describe.concurrent(/* ... */) 926 - describe.skip.concurrent(/* ... */) // or describe.concurrent.skip(/* ... */) 927 - describe.only.concurrent(/* ... */) // or describe.concurrent.only(/* ... */) 928 - describe.todo.concurrent(/* ... */) // or describe.concurrent.todo(/* ... */) 929 - ``` 930 - 931 - When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context) to ensure the right test is detected. 932 - 933 - ```ts 934 - describe.concurrent('suite', () => { 935 - test('concurrent test 1', async ({ expect }) => { 936 - expect(foo).toMatchSnapshot() 937 - }) 938 - test('concurrent test 2', async ({ expect }) => { 939 - expect(foo).toMatchSnapshot() 940 - }) 941 - }) 942 - ``` 943 - 944 - ::: warning 945 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 946 - ::: 947 - 948 - ### describe.sequential 949 - 950 - - **Alias:** `suite.sequential` 951 - 952 - `describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 953 - 954 - ```ts 955 - import { describe, test } from 'vitest' 956 - 957 - describe.concurrent('suite', () => { 958 - test('concurrent test 1', async () => { /* ... */ }) 959 - test('concurrent test 2', async () => { /* ... */ }) 960 - 961 - describe.sequential('', () => { 962 - test('sequential test 1', async () => { /* ... */ }) 963 - test('sequential test 2', async () => { /* ... */ }) 964 - }) 965 - }) 966 - ``` 967 - 968 - ### describe.shuffle 969 - 970 - - **Alias:** `suite.shuffle` 971 - 972 - Vitest provides a way to run all tests in random order via CLI flag [`--sequence.shuffle`](/guide/cli) or config option [`sequence.shuffle`](/config/#sequence-shuffle), but if you want to have only part of your test suite to run tests in random order, you can mark it with this flag. 973 - 974 - ```ts 975 - import { describe, test } from 'vitest' 976 - 977 - // or describe('suite', { shuffle: true }, ...) 978 - describe.shuffle('suite', () => { 979 - test('random test 1', async () => { /* ... */ }) 980 - test('random test 2', async () => { /* ... */ }) 981 - test('random test 3', async () => { /* ... */ }) 982 - 983 - // `shuffle` is inherited 984 - describe('still random', () => { 985 - test('random 4.1', async () => { /* ... */ }) 986 - test('random 4.2', async () => { /* ... */ }) 987 - }) 988 - 989 - // disable shuffle inside 990 - describe('not random', { shuffle: false }, () => { 991 - test('in order 5.1', async () => { /* ... */ }) 992 - test('in order 5.2', async () => { /* ... */ }) 993 - }) 994 - }) 995 - // order depends on sequence.seed option in config (Date.now() by default) 996 - ``` 997 - 998 - `.skip`, `.only`, and `.todo` works with random suites. 999 - 1000 - ::: warning 1001 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 1002 - ::: 1003 - 1004 - ### describe.todo 1005 - 1006 - - **Alias:** `suite.todo` 1007 - 1008 - Use `describe.todo` to stub suites to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. 1009 - 1010 - ```ts 1011 - // An entry will be shown in the report for this suite 1012 - describe.todo('unimplemented suite') 1013 - ``` 1014 - 1015 - ### describe.each 1016 - 1017 - - **Alias:** `suite.each` 1018 - 1019 - ::: tip 1020 - While `describe.each` is provided for Jest compatibility, 1021 - Vitest also has [`describe.for`](#describe-for) which simplifies argument types and aligns with [`test.for`](#test-for). 1022 - ::: 1023 - 1024 - Use `describe.each` if you have more than one test that depends on the same data. 1025 - 1026 - ```ts 1027 - import { describe, expect, test } from 'vitest' 1028 - 1029 - describe.each([ 1030 - { a: 1, b: 1, expected: 2 }, 1031 - { a: 1, b: 2, expected: 3 }, 1032 - { a: 2, b: 1, expected: 3 }, 1033 - ])('describe object add($a, $b)', ({ a, b, expected }) => { 1034 - test(`returns ${expected}`, () => { 1035 - expect(a + b).toBe(expected) 1036 - }) 1037 - 1038 - test(`returned value not be greater than ${expected}`, () => { 1039 - expect(a + b).not.toBeGreaterThan(expected) 1040 - }) 1041 - 1042 - test(`returned value not be less than ${expected}`, () => { 1043 - expect(a + b).not.toBeLessThan(expected) 1044 - }) 1045 - }) 1046 - ``` 1047 - 1048 - * First row should be column names, separated by `|`; 1049 - * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. 1050 - 1051 - ```ts 1052 - import { describe, expect, test } from 'vitest' 1053 - 1054 - describe.each` 1055 - a | b | expected 1056 - ${1} | ${1} | ${2} 1057 - ${'a'} | ${'b'} | ${'ab'} 1058 - ${[]} | ${'b'} | ${'b'} 1059 - ${{}} | ${'b'} | ${'[object Object]b'} 1060 - ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 1061 - `('describe template string add($a, $b)', ({ a, b, expected }) => { 1062 - test(`returns ${expected}`, () => { 1063 - expect(a + b).toBe(expected) 1064 - }) 1065 - }) 1066 - ``` 1067 - 1068 - ::: warning 1069 - You cannot use this syntax when using Vitest as [type checker](/guide/testing-types). 1070 - ::: 1071 - 1072 - ### describe.for 1073 - 1074 - - **Alias:** `suite.for` 1075 - 1076 - The difference from `describe.each` is how array case is provided in the arguments. 1077 - Other non array case (including template string usage) works exactly same. 1078 - 1079 - ```ts 1080 - // `each` spreads array case 1081 - describe.each([ 1082 - [1, 1, 2], 1083 - [1, 2, 3], 1084 - [2, 1, 3], 1085 - ])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] 1086 - test('test', () => { 1087 - expect(a + b).toBe(expected) 1088 - }) 1089 - }) 1090 - 1091 - // `for` doesn't spread array case 1092 - describe.for([ 1093 - [1, 1, 2], 1094 - [1, 2, 3], 1095 - [2, 1, 3], 1096 - ])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] 1097 - test('test', () => { 1098 - expect(a + b).toBe(expected) 1099 - }) 1100 - }) 1101 - ``` 1102 - 1103 - ## Setup and Teardown 1104 - 1105 - These functions allow you to hook into the life cycle of tests to avoid repeating setup and teardown code. They apply to the current context: the file if they are used at the top-level or the current suite if they are inside a `describe` block. These hooks are not called, when you are running Vitest as a type checker. 1106 - 1107 - ### beforeEach 1108 - 1109 - - **Type:** `beforeEach(fn: () => Awaitable<void>, timeout?: number)` 1110 - 1111 - Register a callback to be called before each of the tests in the current context runs. 1112 - If the function returns a promise, Vitest waits until the promise resolve before running the test. 1113 - 1114 - Optionally, you can pass a timeout (in milliseconds) defining how long to wait before terminating. The default is 5 seconds. 1115 - 1116 - ```ts 1117 - import { beforeEach } from 'vitest' 1118 - 1119 - beforeEach(async () => { 1120 - // Clear mocks and add some testing data before each test run 1121 - await stopMocking() 1122 - await addUser({ name: 'John' }) 1123 - }) 1124 - ``` 1125 - 1126 - Here, the `beforeEach` ensures that user is added for each test. 1127 - 1128 - `beforeEach` also accepts an optional cleanup function (equivalent to `afterEach`). 1129 - 1130 - ```ts 1131 - import { beforeEach } from 'vitest' 1132 - 1133 - beforeEach(async () => { 1134 - // called once before each test run 1135 - await prepareSomething() 1136 - 1137 - // clean up function, called once after each test run 1138 - return async () => { 1139 - await resetSomething() 1140 - } 1141 - }) 1142 - ``` 1143 - 1144 - ### afterEach 1145 - 1146 - - **Type:** `afterEach(fn: () => Awaitable<void>, timeout?: number)` 1147 - 1148 - Register a callback to be called after each one of the tests in the current context completes. 1149 - If the function returns a promise, Vitest waits until the promise resolve before continuing. 1150 - 1151 - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. 1152 - 1153 - ```ts 1154 - import { afterEach } from 'vitest' 1155 - 1156 - afterEach(async () => { 1157 - await clearTestingData() // clear testing data after each test run 1158 - }) 1159 - ``` 1160 - 1161 - Here, the `afterEach` ensures that testing data is cleared after each test runs. 1162 - 1163 - ::: tip 1164 - You can also use [`onTestFinished`](#ontestfinished) during the test execution to cleanup any state after the test has finished running. 1165 - ::: 1166 - 1167 - ### beforeAll 1168 - 1169 - - **Type:** `beforeAll(fn: () => Awaitable<void>, timeout?: number)` 1170 - 1171 - Register a callback to be called once before starting to run all tests in the current context. 1172 - If the function returns a promise, Vitest waits until the promise resolve before running tests. 1173 - 1174 - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. 1175 - 1176 - ```ts 1177 - import { beforeAll } from 'vitest' 1178 - 1179 - beforeAll(async () => { 1180 - await startMocking() // called once before all tests run 1181 - }) 1182 - ``` 1183 - 1184 - Here the `beforeAll` ensures that the mock data is set up before tests run. 1185 - 1186 - `beforeAll` also accepts an optional cleanup function (equivalent to `afterAll`). 1187 - 1188 - ```ts 1189 - import { beforeAll } from 'vitest' 1190 - 1191 - beforeAll(async () => { 1192 - // called once before all tests run 1193 - await startMocking() 1194 - 1195 - // clean up function, called once after all tests run 1196 - return async () => { 1197 - await stopMocking() 1198 - } 1199 - }) 1200 - ``` 1201 - 1202 - ### afterAll 1203 - 1204 - - **Type:** `afterAll(fn: () => Awaitable<void>, timeout?: number)` 1205 - 1206 - Register a callback to be called once after all tests have run in the current context. 1207 - If the function returns a promise, Vitest waits until the promise resolve before continuing. 1208 - 1209 - Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating. The default is 5 seconds. 1210 - 1211 - ```ts 1212 - import { afterAll } from 'vitest' 1213 - 1214 - afterAll(async () => { 1215 - await stopMocking() // this method is called after all tests run 1216 - }) 1217 - ``` 1218 - 1219 - Here the `afterAll` ensures that `stopMocking` method is called after all tests run. 1220 - 1221 - ## Test Hooks 1222 - 1223 - Vitest provides a few hooks that you can call _during_ the test execution to cleanup the state when the test has finished running. 1224 - 1225 - ::: warning 1226 - These hooks will throw an error if they are called outside of the test body. 1227 - ::: 1228 - 1229 - ### onTestFinished {#ontestfinished} 1230 - 1231 - This hook is always called after the test has finished running. It is called after `afterEach` hooks since they can influence the test result. It receives an `ExtendedContext` object like `beforeEach` and `afterEach`. 1232 - 1233 - ```ts {1,5} 1234 - import { onTestFinished, test } from 'vitest' 1235 - 1236 - test('performs a query', () => { 1237 - const db = connectDb() 1238 - onTestFinished(() => db.close()) 1239 - db.query('SELECT * FROM users') 1240 - }) 1241 - ``` 1242 - 1243 - ::: warning 1244 - If you are running tests concurrently, you should always use `onTestFinished` hook from the test context since Vitest doesn't track concurrent tests in global hooks: 1245 - 1246 - ```ts {3,5} 1247 - import { test } from 'vitest' 1248 - 1249 - test.concurrent('performs a query', ({ onTestFinished }) => { 1250 - const db = connectDb() 1251 - onTestFinished(() => db.close()) 1252 - db.query('SELECT * FROM users') 1253 - }) 1254 - ``` 1255 - ::: 1256 - 1257 - This hook is particularly useful when creating reusable logic: 1258 - 1259 - ```ts 1260 - // this can be in a separate file 1261 - function getTestDb() { 1262 - const db = connectMockedDb() 1263 - onTestFinished(() => db.close()) 1264 - return db 1265 - } 1266 - 1267 - test('performs a user query', async () => { 1268 - const db = getTestDb() 1269 - expect( 1270 - await db.query('SELECT * from users').perform() 1271 - ).toEqual([]) 1272 - }) 1273 - 1274 - test('performs an organization query', async () => { 1275 - const db = getTestDb() 1276 - expect( 1277 - await db.query('SELECT * from organizations').perform() 1278 - ).toEqual([]) 1279 - }) 1280 - ``` 1281 - 1282 - ::: tip 1283 - This hook is always called in reverse order and is not affected by [`sequence.hooks`](/config/#sequence-hooks) option. 1284 - ::: 1285 - 1286 - ### onTestFailed 1287 - 1288 - This hook is called only after the test has failed. It is called after `afterEach` hooks since they can influence the test result. It receives an `ExtendedContext` object like `beforeEach` and `afterEach`. This hook is useful for debugging. 1289 - 1290 - ```ts {1,5-7} 1291 - import { onTestFailed, test } from 'vitest' 1292 - 1293 - test('performs a query', () => { 1294 - const db = connectDb() 1295 - onTestFailed(({ task }) => { 1296 - console.log(task.result.errors) 1297 - }) 1298 - db.query('SELECT * FROM users') 1299 - }) 1300 - ``` 1301 - 1302 - ::: warning 1303 - If you are running tests concurrently, you should always use `onTestFailed` hook from the test context since Vitest doesn't track concurrent tests in global hooks: 1304 - 1305 - ```ts {3,5-7} 1306 - import { test } from 'vitest' 1307 - 1308 - test.concurrent('performs a query', ({ onTestFailed }) => { 1309 - const db = connectDb() 1310 - onTestFailed(({ task }) => { 1311 - console.log(task.result.errors) 1312 - }) 1313 - db.query('SELECT * FROM users') 1314 - }) 1315 - ``` 1316 - :::
+857
docs/api/test.md
··· 1 + --- 2 + outline: deep 3 + --- 4 + 5 + # Test 6 + 7 + - **Alias:** `it` 8 + 9 + ```ts 10 + function test( 11 + name: string | Function, 12 + body?: () => unknown, 13 + timeout?: number 14 + ): void 15 + function test( 16 + name: string | Function, 17 + options: TestOptions, 18 + body?: () => unknown, 19 + ): void 20 + ``` 21 + 22 + `test` or `it` defines a set of related expectations. It receives the test name and a function that holds the expectations to test. 23 + 24 + Optionally, you can provide a timeout (in milliseconds) for specifying how long to wait before terminating, or a set of [additional options](#test-options). The default timeout is 5 seconds, and can be configured globally with [`testTimeout`](/config/testtimeout). 25 + 26 + ```ts 27 + import { expect, test } from 'vitest' 28 + 29 + test('should work as expected', () => { 30 + expect(Math.sqrt(4)).toBe(2) 31 + }) 32 + ``` 33 + 34 + ::: warning 35 + If the first argument is a function, its `name` property will be used as the name of the test. The function itself will not be called. 36 + 37 + If test body is not provided, the test is marked as `todo`. 38 + ::: 39 + 40 + When a test function returns a promise, the runner will wait until it is resolved to collect async expectations. If the promise is rejected, the test will fail. 41 + 42 + ::: tip 43 + In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If this form is used, the test will not be concluded until `done` is called. You can achieve the same using an `async` function, see the [Migration guide Done Callback section](/guide/migration#done-callback). 44 + ::: 45 + 46 + ## Test Options 47 + 48 + You can define boolean options by chaining properties on a function: 49 + 50 + ```ts 51 + import { test } from 'vitest' 52 + 53 + test.skip('skipped test', () => { 54 + // some logic that fails right now 55 + }) 56 + 57 + test.concurrent.skip('skipped concurrent test', () => { 58 + // some logic that fails right now 59 + }) 60 + ``` 61 + 62 + But you can also provide an object as a second argument instead: 63 + 64 + ```ts 65 + import { test } from 'vitest' 66 + 67 + test('skipped test', { skip: true }, () => { 68 + // some logic that fails right now 69 + }) 70 + 71 + test('skipped concurrent test', { skip: true, concurrent: true }, () => { 72 + // some logic that fails right now 73 + }) 74 + ``` 75 + 76 + They both work in exactly the same way. To use either one is purely a stylistic choice. 77 + 78 + ### timeout 79 + 80 + - **Type:** `number` 81 + - **Default:** `5_000` (configured by [`testTimeout`](/config/testtimeout)) 82 + 83 + Test timeout in milliseconds. 84 + 85 + ::: warning 86 + Note that if you are providing timeout as the last argument, you cannot use options anymore: 87 + 88 + ```ts 89 + import { test } from 'vitest' 90 + 91 + // ✅ this works 92 + test.skip('heavy test', () => { 93 + // ... 94 + }, 10_000) 95 + 96 + // ❌ this doesn't work 97 + test('heavy test', { skip: true }, () => { 98 + // ... 99 + }, 10_000) 100 + ``` 101 + 102 + However, you can provide a timeout inside the object: 103 + 104 + ```ts 105 + import { test } from 'vitest' 106 + 107 + // ✅ this works 108 + test('heavy test', { skip: true, timeout: 10_000 }, () => { 109 + // ... 110 + }) 111 + ``` 112 + ::: 113 + 114 + ### retry 115 + 116 + - **Default:** `0` (configured by [`retry`](/config/retry)) 117 + - **Type:** 118 + 119 + ```ts 120 + type Retry = number | { 121 + /** 122 + * The number of times to retry the test if it fails. 123 + * @default 0 124 + */ 125 + count?: number 126 + /** 127 + * Delay in milliseconds between retry attempts. 128 + * @default 0 129 + */ 130 + delay?: number 131 + /** 132 + * Condition to determine if a test should be retried based on the error. 133 + * - If a RegExp, it is tested against the error message 134 + * - If a function, called with the TestError object; return true to retry 135 + * 136 + * NOTE: Functions can only be used in test files, not in vitest.config.ts, 137 + * because the configuration is serialized when passed to worker threads. 138 + * 139 + * @default undefined (retry on all errors) 140 + */ 141 + condition?: RegExp | ((error: TestError) => boolean) 142 + } 143 + ``` 144 + 145 + Retry configuration for the test. If a number, specifies how many times to retry. If an object, allows fine-grained retry control. 146 + 147 + Note that the object configuration is available only since Vitest 4.1. 148 + 149 + ### repeats 150 + 151 + - **Type:** `number` 152 + - **Default:** `0` 153 + 154 + How many times the test will run again. If set to `0` (the default), the test will run only one time. 155 + 156 + This can be useful for debugging flaky tests. 157 + 158 + ### tags <Version>4.1.0</Version> {#tags} 159 + 160 + - **Type:** `string[]` 161 + - **Default:** `[]` 162 + 163 + Custom user [tags](/guide/test-tags). If the tag is not specified in the [configuration](/config/tags), the test will fail before it starts, unless [`strictTags`](/config/stricttags) is disabled manually. 164 + 165 + ```ts 166 + import { it } from 'vitest' 167 + 168 + it('user returns data from db', { tags: ['db', 'flaky'] }, () => { 169 + // ... 170 + }) 171 + ``` 172 + 173 + ### concurrent 174 + 175 + - **Type:** `boolean` 176 + - **Default:** `false` (configured by [`sequence.concurrent`](/config/sequence#sequence-concurrent)) 177 + - **Alias:** [`test.concurrent`](#test-concurrent) 178 + 179 + Whether this test run concurrently with other concurrent tests in the suite. 180 + 181 + ### sequential 182 + 183 + - **Type:** `boolean` 184 + - **Default:** `true` 185 + - **Alias:** [`test.sequential`](#test-sequential) 186 + 187 + Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precendence. 188 + 189 + ### skip 190 + 191 + - **Type:** `boolean` 192 + - **Default:** `false` 193 + - **Alias:** [`test.skip`](#test-skip) 194 + 195 + Whether the test should be skipped. 196 + 197 + ### only 198 + 199 + - **Type:** `boolean` 200 + - **Default:** `false` 201 + - **Alias:** [`test.only`](#test-only) 202 + 203 + Should this test be the only one running in a suite. 204 + 205 + ### todo 206 + 207 + - **Type:** `boolean` 208 + - **Default:** `false` 209 + - **Alias:** [`test.todo`](#test-todo) 210 + 211 + Whether the test should be skipped and marked as a todo. 212 + 213 + ### fails 214 + 215 + - **Type:** `boolean` 216 + - **Default:** `false` 217 + - **Alias:** [`test.fails`](#test-fails) 218 + 219 + Whether the test is expected to fail. If it does, the test will pass, otherwise it will fail. 220 + 221 + ## test.extend 222 + 223 + - **Alias:** `it.extend` 224 + 225 + Use `test.extend` to extend the test context with custom fixtures. This will return a new `test` and it's also extendable, so you can compose more fixtures or override existing ones by extending it as you need. See [Extend Test Context](/guide/test-context.html#test-extend) for more information. 226 + 227 + ```ts 228 + import { test as baseTest, expect } from 'vitest' 229 + 230 + const todos = [] 231 + const archive = [] 232 + 233 + const test = baseTest.extend({ 234 + todos: async ({ task }, use) => { 235 + todos.push(1, 2, 3) 236 + await use(todos) 237 + todos.length = 0 238 + }, 239 + archive, 240 + }) 241 + 242 + test('add item', ({ todos }) => { 243 + expect(todos.length).toBe(3) 244 + 245 + todos.push(4) 246 + expect(todos.length).toBe(4) 247 + }) 248 + ``` 249 + 250 + ## test.skip 251 + 252 + - **Alias:** `it.skip` 253 + 254 + If you want to skip running certain tests, but you don't want to delete the code due to any reason, you can use `test.skip` to avoid running them. 255 + 256 + ```ts 257 + import { assert, test } from 'vitest' 258 + 259 + test.skip('skipped test', () => { 260 + // Test skipped, no error 261 + assert.equal(Math.sqrt(4), 3) 262 + }) 263 + ``` 264 + 265 + You can also skip test by calling `skip` on its [context](/guide/test-context) dynamically: 266 + 267 + ```ts 268 + import { assert, test } from 'vitest' 269 + 270 + test('skipped test', (context) => { 271 + context.skip() 272 + // Test skipped, no error 273 + assert.equal(Math.sqrt(4), 3) 274 + }) 275 + ``` 276 + 277 + If the condition is unknown, you can provide it to the `skip` method as the first arguments: 278 + 279 + ```ts 280 + import { assert, test } from 'vitest' 281 + 282 + test('skipped test', (context) => { 283 + context.skip(Math.random() < 0.5, 'optional message') 284 + // Test skipped, no error 285 + assert.equal(Math.sqrt(4), 3) 286 + }) 287 + ``` 288 + 289 + ## test.skipIf 290 + 291 + - **Alias:** `it.skipIf` 292 + 293 + In some cases you might run tests multiple times with different environments, and some of the tests might be environment-specific. Instead of wrapping the test code with `if`, you can use `test.skipIf` to skip the test whenever the condition is truthy. 294 + 295 + ```ts 296 + import { assert, test } from 'vitest' 297 + 298 + const isDev = process.env.NODE_ENV === 'development' 299 + 300 + test.skipIf(isDev)('prod only test', () => { 301 + // this test only runs in production 302 + }) 303 + ``` 304 + 305 + ## test.runIf 306 + 307 + - **Alias:** `it.runIf` 308 + 309 + Opposite of [test.skipIf](#test-skipif). 310 + 311 + ```ts 312 + import { assert, test } from 'vitest' 313 + 314 + const isDev = process.env.NODE_ENV === 'development' 315 + 316 + test.runIf(isDev)('dev only test', () => { 317 + // this test only runs in development 318 + }) 319 + ``` 320 + 321 + ## test.only 322 + 323 + - **Alias:** `it.only` 324 + 325 + Use `test.only` to only run certain tests in a given suite. This is useful when debugging. 326 + 327 + ```ts 328 + import { assert, test } from 'vitest' 329 + 330 + test.only('test', () => { 331 + // Only this test (and others marked with only) are run 332 + assert.equal(Math.sqrt(4), 2) 333 + }) 334 + ``` 335 + 336 + Sometimes it is very useful to run `only` tests in a certain file, ignoring all other tests from the whole test suite, which pollute the output. 337 + 338 + In order to do that, run `vitest` with specific file containing the tests in question: 339 + 340 + ```shell 341 + vitest interesting.test.ts 342 + ``` 343 + 344 + ::: warning 345 + Vitest detects when tests are running in CI and will throw an error if any test has `only` flag. You can configure this behaviour via [`allowOnly`](/config/allowonly) option. 346 + ::: 347 + 348 + ## test.concurrent 349 + 350 + - **Alias:** `it.concurrent` 351 + 352 + `test.concurrent` marks consecutive tests to be run in parallel. It receives the test name, an async function with the tests to collect, and an optional timeout (in milliseconds). 353 + 354 + ```ts 355 + import { describe, test } from 'vitest' 356 + 357 + // The two tests marked with concurrent will be run in parallel 358 + describe('suite', () => { 359 + test('serial test', async () => { /* ... */ }) 360 + test.concurrent('concurrent test 1', async () => { /* ... */ }) 361 + test.concurrent('concurrent test 2', async () => { /* ... */ }) 362 + }) 363 + ``` 364 + 365 + `test.skip`, `test.only`, and `test.todo` works with concurrent tests. All the following combinations are valid: 366 + 367 + ```ts 368 + test.concurrent(/* ... */) 369 + test.skip.concurrent(/* ... */) // or test.concurrent.skip(/* ... */) 370 + test.only.concurrent(/* ... */) // or test.concurrent.only(/* ... */) 371 + test.todo.concurrent(/* ... */) // or test.concurrent.todo(/* ... */) 372 + ``` 373 + 374 + When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context.md) to ensure the right test is detected. 375 + 376 + ```ts 377 + test.concurrent('test 1', async ({ expect }) => { 378 + expect(foo).toMatchSnapshot() 379 + }) 380 + test.concurrent('test 2', async ({ expect }) => { 381 + expect(foo).toMatchSnapshot() 382 + }) 383 + ``` 384 + 385 + Note that if tests are synchronous, Vitest will still run them sequentially. 386 + 387 + ## test.sequential 388 + 389 + - **Alias:** `it.sequential` 390 + 391 + `test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. 392 + 393 + ```ts 394 + import { describe, test } from 'vitest' 395 + 396 + // with config option { sequence: { concurrent: true } } 397 + test('concurrent test 1', async () => { /* ... */ }) 398 + test('concurrent test 2', async () => { /* ... */ }) 399 + 400 + test.sequential('sequential test 1', async () => { /* ... */ }) 401 + test.sequential('sequential test 2', async () => { /* ... */ }) 402 + 403 + // within concurrent suite 404 + describe.concurrent('suite', () => { 405 + test('concurrent test 1', async () => { /* ... */ }) 406 + test('concurrent test 2', async () => { /* ... */ }) 407 + 408 + test.sequential('sequential test 1', async () => { /* ... */ }) 409 + test.sequential('sequential test 2', async () => { /* ... */ }) 410 + }) 411 + ``` 412 + 413 + ## test.todo 414 + 415 + - **Alias:** `it.todo` 416 + 417 + Use `test.todo` to stub tests to be implemented later. An entry will be shown in the report for the tests so you know how many tests you still need to implement. 418 + 419 + ```ts 420 + // An entry will be shown in the report for this test 421 + test.todo('unimplemented test', () => { 422 + // failing implementation... 423 + }) 424 + ``` 425 + 426 + ::: tip 427 + Vitest will automatically mark test as `todo` if test has no body. 428 + ::: 429 + 430 + ## test.fails 431 + 432 + - **Alias:** `it.fails` 433 + 434 + Use `test.fails` to indicate that an assertion will fail explicitly. 435 + 436 + ```ts 437 + import { expect, test } from 'vitest' 438 + 439 + test.fails('repro #1234', () => { 440 + expect(add(1, 2)).toBe(4) 441 + }) 442 + ``` 443 + 444 + This flag is useful to track difference in behaviour of your library over time. For example, you can define a failing test without fixing the issue yet due to time constraints. Tests marked with `fails` are tracked in the test summary since Vitest 4.1. 445 + 446 + ## test.each 447 + 448 + - **Alias:** `it.each` 449 + 450 + ::: tip 451 + While `test.each` is provided for Jest compatibility, 452 + Vitest also has [`test.for`](#test-for) with an additional feature to integrate [`TestContext`](/guide/test-context). 453 + ::: 454 + 455 + Use `test.each` when you need to run the same test with different variables. 456 + You can inject parameters with [printf formatting](https://nodejs.org/api/util.html#util_util_format_format_args) in the test name in the order of the test function parameters. 457 + 458 + - `%s`: string 459 + - `%d`: number 460 + - `%i`: integer 461 + - `%f`: floating point value 462 + - `%j`: json 463 + - `%o`: object 464 + - `%#`: 0-based index of the test case 465 + - `%$`: 1-based index of the test case 466 + - `%%`: single percent sign ('%') 467 + 468 + ```ts 469 + import { expect, test } from 'vitest' 470 + 471 + test.each([ 472 + [1, 1, 2], 473 + [1, 2, 3], 474 + [2, 1, 3], 475 + ])('add(%i, %i) -> %i', (a, b, expected) => { 476 + expect(a + b).toBe(expected) 477 + }) 478 + 479 + // this will return 480 + // ✓ add(1, 1) -> 2 481 + // ✓ add(1, 2) -> 3 482 + // ✓ add(2, 1) -> 3 483 + ``` 484 + 485 + You can also access object properties and array elements with `$` prefix: 486 + 487 + ```ts 488 + test.each([ 489 + { a: 1, b: 1, expected: 2 }, 490 + { a: 1, b: 2, expected: 3 }, 491 + { a: 2, b: 1, expected: 3 }, 492 + ])('add($a, $b) -> $expected', ({ a, b, expected }) => { 493 + expect(a + b).toBe(expected) 494 + }) 495 + 496 + // this will return 497 + // ✓ add(1, 1) -> 2 498 + // ✓ add(1, 2) -> 3 499 + // ✓ add(2, 1) -> 3 500 + 501 + test.each([ 502 + [1, 1, 2], 503 + [1, 2, 3], 504 + [2, 1, 3], 505 + ])('add($0, $1) -> $2', (a, b, expected) => { 506 + expect(a + b).toBe(expected) 507 + }) 508 + 509 + // this will return 510 + // ✓ add(1, 1) -> 2 511 + // ✓ add(1, 2) -> 3 512 + // ✓ add(2, 1) -> 3 513 + ``` 514 + 515 + You can also access Object attributes with `.`, if you are using objects as arguments: 516 + 517 + ```ts 518 + test.each` 519 + a | b | expected 520 + ${{ val: 1 }} | ${'b'} | ${'1b'} 521 + ${{ val: 2 }} | ${'b'} | ${'2b'} 522 + ${{ val: 3 }} | ${'b'} | ${'3b'} 523 + `('add($a.val, $b) -> $expected', ({ a, b, expected }) => { 524 + expect(a.val + b).toBe(expected) 525 + }) 526 + 527 + // this will return 528 + // ✓ add(1, b) -> 1b 529 + // ✓ add(2, b) -> 2b 530 + // ✓ add(3, b) -> 3b 531 + ``` 532 + 533 + * First row should be column names, separated by `|`; 534 + * One or more subsequent rows of data supplied as template literal expressions using `${value}` syntax. 535 + 536 + ```ts 537 + import { expect, test } from 'vitest' 538 + 539 + test.each` 540 + a | b | expected 541 + ${1} | ${1} | ${2} 542 + ${'a'} | ${'b'} | ${'ab'} 543 + ${[]} | ${'b'} | ${'b'} 544 + ${{}} | ${'b'} | ${'[object Object]b'} 545 + ${{ asd: 1 }} | ${'b'} | ${'[object Object]b'} 546 + `('returns $expected when $a is added $b', ({ a, b, expected }) => { 547 + expect(a + b).toBe(expected) 548 + }) 549 + ``` 550 + 551 + ::: tip 552 + Vitest processes `$values` with Chai `format` method. If the value is too truncated, you can increase [chaiConfig.truncateThreshold](/config/#chaiconfig-truncatethreshold) in your config file. 553 + ::: 554 + 555 + ## test.for 556 + 557 + - **Alias:** `it.for` 558 + 559 + Alternative to `test.each` to provide [`TestContext`](/guide/test-context). 560 + 561 + The difference from `test.each` lies in how arrays are provided in the arguments. 562 + Non-array arguments to `test.for` (including template string usage) work exactly the same as for `test.each`. 563 + 564 + ```ts 565 + // `each` spreads arrays 566 + test.each([ 567 + [1, 1, 2], 568 + [1, 2, 3], 569 + [2, 1, 3], 570 + ])('add(%i, %i) -> %i', (a, b, expected) => { // [!code --] 571 + expect(a + b).toBe(expected) 572 + }) 573 + 574 + // `for` doesn't spread arrays (notice the square brackets around the arguments) 575 + test.for([ 576 + [1, 1, 2], 577 + [1, 2, 3], 578 + [2, 1, 3], 579 + ])('add(%i, %i) -> %i', ([a, b, expected]) => { // [!code ++] 580 + expect(a + b).toBe(expected) 581 + }) 582 + ``` 583 + 584 + The 2nd argument is [`TestContext`](/guide/test-context) and can be used for concurrent snapshots, for example: 585 + 586 + ```ts 587 + test.concurrent.for([ 588 + [1, 1], 589 + [1, 2], 590 + [2, 1], 591 + ])('add(%i, %i)', ([a, b], { expect }) => { 592 + expect(a + b).toMatchSnapshot() 593 + }) 594 + ``` 595 + 596 + ## test.describe <Version>4.1.0</Version> {#test-describe} 597 + 598 + Scoped `describe`. See [describe](/api/describe) for more information. 599 + 600 + ## test.beforeEach 601 + 602 + Scoped `beforeEach` hook that inherits types from [`test.extend`](#test-extend). See [beforeEach](/api/hooks#beforeeach) for more information. 603 + 604 + ## test.afterEach 605 + 606 + Scoped `afterEach` hook that inherits types from [`test.extend`](#test-extend). See [afterEach](/api/hooks#aftereach) for more information. 607 + 608 + ## test.beforeAll 609 + 610 + Scoped `beforeAll` hook. See [beforeAll](/api/hooks#beforeall) for more information. 611 + 612 + ## test.afterAll 613 + 614 + Scoped `afterAll` hook. See [afterAll](/api/hooks#afterall) for more information. 615 + 616 + ## test.aroundEach <Version>4.1.0</Version> {#test-aroundeach} 617 + 618 + Scoped `aroundEach` hook that inherits types from [`test.extend`](#test-extend). See [aroundEach](/api/hooks#aroundeach) for more information. 619 + 620 + ## test.aroundAll <Version>4.1.0</Version> {#test-aroundall} 621 + 622 + Scoped `aroundAll` hook. See [aroundAll](/api/hooks#aroundall) for more information. 623 + 624 + ## bench <Experimental /> {#bench} 625 + 626 + - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 627 + 628 + ::: danger 629 + Benchmarking is experimental and does not follow SemVer. 630 + ::: 631 + 632 + `bench` defines a benchmark. In Vitest terms, benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results. 633 + 634 + Vitest uses the [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. 635 + 636 + ```ts 637 + import { bench } from 'vitest' 638 + 639 + bench('normal sorting', () => { 640 + const x = [1, 5, 4, 2, 3] 641 + x.sort((a, b) => { 642 + return a - b 643 + }) 644 + }, { time: 1000 }) 645 + ``` 646 + 647 + ```ts 648 + export interface Options { 649 + /** 650 + * time needed for running a benchmark task (milliseconds) 651 + * @default 500 652 + */ 653 + time?: number 654 + 655 + /** 656 + * number of times that a task should run if even the time option is finished 657 + * @default 10 658 + */ 659 + iterations?: number 660 + 661 + /** 662 + * function to get the current timestamp in milliseconds 663 + */ 664 + now?: () => number 665 + 666 + /** 667 + * An AbortSignal for aborting the benchmark 668 + */ 669 + signal?: AbortSignal 670 + 671 + /** 672 + * Throw if a task fails (events will not work if true) 673 + */ 674 + throws?: boolean 675 + 676 + /** 677 + * warmup time (milliseconds) 678 + * @default 100ms 679 + */ 680 + warmupTime?: number 681 + 682 + /** 683 + * warmup iterations 684 + * @default 5 685 + */ 686 + warmupIterations?: number 687 + 688 + /** 689 + * setup function to run before each benchmark task (cycle) 690 + */ 691 + setup?: Hook 692 + 693 + /** 694 + * teardown function to run after each benchmark task (cycle) 695 + */ 696 + teardown?: Hook 697 + } 698 + ``` 699 + After the test case is run, the output structure information is as follows: 700 + 701 + ``` 702 + name hz min max mean p75 p99 p995 p999 rme samples 703 + · normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638 704 + ``` 705 + ```ts 706 + export interface TaskResult { 707 + /* 708 + * the last error that was thrown while running the task 709 + */ 710 + error?: unknown 711 + 712 + /** 713 + * The amount of time in milliseconds to run the benchmark task (cycle). 714 + */ 715 + totalTime: number 716 + 717 + /** 718 + * the minimum value in the samples 719 + */ 720 + min: number 721 + /** 722 + * the maximum value in the samples 723 + */ 724 + max: number 725 + 726 + /** 727 + * the number of operations per second 728 + */ 729 + hz: number 730 + 731 + /** 732 + * how long each operation takes (ms) 733 + */ 734 + period: number 735 + 736 + /** 737 + * task samples of each task iteration time (ms) 738 + */ 739 + samples: number[] 740 + 741 + /** 742 + * samples mean/average (estimate of the population mean) 743 + */ 744 + mean: number 745 + 746 + /** 747 + * samples variance (estimate of the population variance) 748 + */ 749 + variance: number 750 + 751 + /** 752 + * samples standard deviation (estimate of the population standard deviation) 753 + */ 754 + sd: number 755 + 756 + /** 757 + * standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) 758 + */ 759 + sem: number 760 + 761 + /** 762 + * degrees of freedom 763 + */ 764 + df: number 765 + 766 + /** 767 + * critical value of the samples 768 + */ 769 + critical: number 770 + 771 + /** 772 + * margin of error 773 + */ 774 + moe: number 775 + 776 + /** 777 + * relative margin of error 778 + */ 779 + rme: number 780 + 781 + /** 782 + * median absolute deviation 783 + */ 784 + mad: number 785 + 786 + /** 787 + * p50/median percentile 788 + */ 789 + p50: number 790 + 791 + /** 792 + * p75 percentile 793 + */ 794 + p75: number 795 + 796 + /** 797 + * p99 percentile 798 + */ 799 + p99: number 800 + 801 + /** 802 + * p995 percentile 803 + */ 804 + p995: number 805 + 806 + /** 807 + * p999 percentile 808 + */ 809 + p999: number 810 + } 811 + ``` 812 + 813 + ### bench.skip 814 + 815 + - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 816 + 817 + You can use `bench.skip` syntax to skip running certain benchmarks. 818 + 819 + ```ts 820 + import { bench } from 'vitest' 821 + 822 + bench.skip('normal sorting', () => { 823 + const x = [1, 5, 4, 2, 3] 824 + x.sort((a, b) => { 825 + return a - b 826 + }) 827 + }) 828 + ``` 829 + 830 + ### bench.only 831 + 832 + - **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` 833 + 834 + Use `bench.only` to only run certain benchmarks in a given suite. This is useful when debugging. 835 + 836 + ```ts 837 + import { bench } from 'vitest' 838 + 839 + bench.only('normal sorting', () => { 840 + const x = [1, 5, 4, 2, 3] 841 + x.sort((a, b) => { 842 + return a - b 843 + }) 844 + }) 845 + ``` 846 + 847 + ### bench.todo 848 + 849 + - **Type:** `(name: string | Function) => void` 850 + 851 + Use `bench.todo` to stub benchmarks to be implemented later. 852 + 853 + ```ts 854 + import { bench } from 'vitest' 855 + 856 + bench.todo('unimplemented test') 857 + ```
+1 -1
docs/api/vi.md
··· 628 628 ::: 629 629 630 630 ::: tip 631 - You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations after every test. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation anymore, unless you spy again: 631 + You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/hooks#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations after every test. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation anymore, unless you spy again: 632 632 633 633 ```ts 634 634 const cart = {
+3 -3
docs/config/allowonly.md
··· 9 9 - **Default**: `!process.env.CI` 10 10 - **CLI:** `--allowOnly`, `--allowOnly=false` 11 11 12 - By default, Vitest does not permit tests marked with the [`only`](/api/#test-only) flag in Continuous Integration (CI) environments. Conversely, in local development environments, Vitest allows these tests to run. 12 + By default, Vitest does not permit tests marked with the [`only`](/api/test#test-only) flag in Continuous Integration (CI) environments. Conversely, in local development environments, Vitest allows these tests to run. 13 13 14 14 ::: info 15 15 Vitest uses [`std-env`](https://www.npmjs.com/package/std-env) package to detect the environment. ··· 32 32 ``` 33 33 ::: 34 34 35 - When enabled, Vitest will not fail the test suite if tests marked with [`only`](/api/#test-only) are detected, including in CI environments. 35 + When enabled, Vitest will not fail the test suite if tests marked with [`only`](/api/test#test-only) are detected, including in CI environments. 36 36 37 - When disabled, Vitest will fail the test suite if tests marked with [`only`](/api/#test-only) are detected, including in local development environments. 37 + When disabled, Vitest will fail the test suite if tests marked with [`only`](/api/test#test-only) are detected, including in local development environments.
+2 -2
docs/config/browser.md
··· 472 472 473 473 - `testName: string` 474 474 475 - The [`test`](/api/#test)'s name, including parent 476 - [`describe`](/api/#describe), sanitized. 475 + The [`test`](/api/test)'s name, including parent 476 + [`describe`](/api/describe), sanitized. 477 477 478 478 - `attachmentsDir: string` 479 479
+1 -1
docs/config/fileparallelism.md
··· 12 12 Should all test files run in parallel. Setting this to `false` will override `maxWorkers` option to `1`. 13 13 14 14 ::: tip 15 - This option doesn't affect tests running in the same file. If you want to run those in parallel, use `concurrent` option on [describe](/api/#describe-concurrent) or via [a config](#sequence-concurrent). 15 + This option doesn't affect tests running in the same file. If you want to run those in parallel, use `concurrent` option on [describe](/api/describe#describe-concurrent) or via [a config](#sequence-concurrent). 16 16 :::
+1 -1
docs/config/sequence.md
··· 148 148 - `parallel` will run hooks in a single group in parallel (hooks in parent suites will still run before the current suite's hooks) 149 149 150 150 ::: tip 151 - This option doesn't affect [`onTestFinished`](/api/#ontestfinished). It is always called in reverse order. 151 + This option doesn't affect [`onTestFinished`](/api/hooks#ontestfinished). It is always called in reverse order. 152 152 ::: 153 153 154 154 ## sequence.setupFiles {#sequence-setupfiles}
+5 -53
docs/config/tags.md
··· 99 99 100 100 ## Test Options 101 101 102 - Tags can define test options that will be applied to every test marked with the tag. These options are merged with the test's own options, with the test's options taking precedence. 102 + Tags can define [test options](/api/test#test-options) that will be applied to every test marked with the tag. These options are merged with the test's own options, with the test's options taking precedence. 103 103 104 - ### timeout 104 + ::: warning 105 + The [`retry.condition`](/api/test#retry) can onle be a regexp because the config values need to be serialised. 105 106 106 - - **Type:** `number` 107 - 108 - Test timeout in milliseconds. 109 - 110 - ### retry 111 - 112 - - **Type:** `number | { count?: number, delay?: number, condition?: RegExp }` 113 - 114 - Retry configuration for the test. If a number, specifies how many times to retry. If an object, allows fine-grained retry control. 115 - 116 - ### repeats 117 - 118 - - **Type:** `number` 119 - 120 - How many times the test will run again. 121 - 122 - ### concurrent 123 - 124 - - **Type:** `boolean` 125 - 126 - Whether suites and tests run concurrently. 127 - 128 - ### sequential 129 - 130 - - **Type:** `boolean` 131 - 132 - Whether tests run sequentially. 133 - 134 - ### skip 135 - 136 - - **Type:** `boolean` 137 - 138 - Whether the test should be skipped. 139 - 140 - ### only 141 - 142 - - **Type:** `boolean` 143 - 144 - Should this test be the only one running in a suite. 145 - 146 - ### todo 147 - 148 - - **Type:** `boolean` 149 - 150 - Whether the test should be skipped and marked as a todo. 151 - 152 - ### fails 153 - 154 - - **Type:** `boolean` 155 - 156 - Whether the test is expected to fail. If it does, the test will pass, otherwise it will fail. 107 + Tags also cannot apply other [tags](/api/test#tags) via these options. 108 + ::: 157 109 158 110 ## Example 159 111
+14
docs/guide/cli-generated.md
··· 844 844 - **Config:** [experimental.printImportBreakdown](/config/experimental#experimental-printimportbreakdown) 845 845 846 846 Print import breakdown after the summary. If the reporter doesn't support summary, this will have no effect. Note that UI's "Module Graph" tab always has an import breakdown. 847 + 848 + ### experimental.viteModuleRunner 849 + 850 + - **CLI:** `--experimental.viteModuleRunner` 851 + - **Config:** [experimental.viteModuleRunner](/config/experimental#experimental-vitemodulerunner) 852 + 853 + Control whether Vitest uses Vite's module runner to run the code or fallback to the native `import`. (default: `true`) 854 + 855 + ### experimental.nodeLoader 856 + 857 + - **CLI:** `--experimental.nodeLoader` 858 + - **Config:** [experimental.nodeLoader](/config/experimental#experimental-nodeloader) 859 + 860 + Controls whether Vitest will use Node.js Loader API to process in-source or mocked files. This has no effect if `viteModuleRunner` is enabled. Disabling this can increase performance. (default: `true`)
+2 -2
docs/guide/features.md
··· 77 77 }) 78 78 ``` 79 79 80 - You can also use `.skip`, `.only`, and `.todo` with concurrent suites and tests. Read more in the [API Reference](/api/#test-concurrent). 80 + You can also use `.skip`, `.only`, and `.todo` with concurrent suites and tests. Read more in the [API Reference](/api/test#test-concurrent). 81 81 82 82 ::: warning 83 83 When running concurrent tests, Snapshots and Assertions must use `expect` from the local [Test Context](/guide/test-context) to ensure the right test is detected. ··· 192 192 193 193 ## Benchmarking <Badge type="warning">Experimental</Badge> {#benchmarking} 194 194 195 - You can run benchmark tests with [`bench`](/api/#bench) function via [Tinybench](https://github.com/tinylibs/tinybench) to compare performance results. 195 + You can run benchmark tests with [`bench`](/api/test#bench) function via [Tinybench](https://github.com/tinylibs/tinybench) to compare performance results. 196 196 197 197 ```ts [sort.bench.ts] 198 198 import { bench, describe } from 'vitest'
+1 -1
docs/guide/index.md
··· 92 92 If you are using Bun as your package manager, make sure to use `bun run test` command instead of `bun test`, otherwise Bun will run its own test runner. 93 93 ::: 94 94 95 - Learn more about the usage of Vitest, see the [API](/api/) section. 95 + Learn more about the usage of Vitest, see the [API](/api/test) section. 96 96 97 97 ## Configuring Vitest 98 98
+3 -3
docs/guide/lifecycle.md
··· 131 131 - `beforeEach` hooks execute (in order defined, or based on [`sequence.hooks`](/config/sequence#sequence-hooks)) 132 132 - Test function executes 133 133 - `afterEach` hooks execute (reverse order by default with `sequence.hooks: 'stack'`) 134 - - [`onTestFinished`](/api/#ontestfinished) callbacks run (always in reverse order) 135 - - If test failed: [`onTestFailed`](/api/#ontestfailed) callbacks run 134 + - [`onTestFinished`](/api/hooks#ontestfinished) callbacks run (always in reverse order) 135 + - If test failed: [`onTestFailed`](/api/hooks#ontestfailed) callbacks run 136 136 - Note: if `repeats` or `retry` are set, all of these steps are executed again 137 137 5. **`afterAll` hooks** - Run once after all tests in the suite complete 138 138 ··· 317 317 - [Isolation Configuration](/config/isolate) 318 318 - [Pool Configuration](/config/pool) 319 319 - [Extending Reporters](/guide/advanced/reporters) - for reporter lifecycle events 320 - - [Test API Reference](/api/) - for hook APIs and test functions 320 + - [Test API Reference](/api/hooks) - for hook APIs
+3 -3
docs/guide/migration.md
··· 319 319 320 320 - `maxThreads` and `maxForks` are now `maxWorkers`. 321 321 - Environment variables `VITEST_MAX_THREADS` and `VITEST_MAX_FORKS` are now `VITEST_MAX_WORKERS`. 322 - - `singleThread` and `singleFork` are now `maxWorkers: 1, isolate: false`. If your tests were relying on module reset between tests, you'll need to add [setupFile](/config/setupfiles) that calls [`vi.resetModules()`](/api/vi.html#vi-resetmodules) in [`beforeAll` test hook](/api/#beforeall). 322 + - `singleThread` and `singleFork` are now `maxWorkers: 1, isolate: false`. If your tests were relying on module reset between tests, you'll need to add [setupFile](/config/setupfiles) that calls [`vi.resetModules()`](/api/vi.html#vi-resetmodules) in [`beforeAll` test hook](/api/hooks#beforeall). 323 323 - `poolOptions` is removed. All previous `poolOptions` are now top-level options. The `memoryLimit` of VM pools is renamed to `vmMemoryLimit`. 324 324 - `threads.useAtomics` is removed. If you have a use case for this, feel free to open a new feature request. 325 325 - Custom pool interface has been rewritten, see [Custom Pool](/guide/advanced/pool#custom-pool) ··· 573 573 574 574 ### Replace property 575 575 576 - If you want to modify the object, you will use [replaceProperty API](https://jestjs.io/docs/jest-object#jestreplacepropertyobject-propertykey-value) in Jest, you can use [`vi.stubEnv`](/api/#vi-stubenv) or [`vi.spyOn`](/api/vi#vi-spyon) to do the same also in Vitest. 576 + If you want to modify the object, you will use [replaceProperty API](https://jestjs.io/docs/jest-object#jestreplacepropertyobject-propertykey-value) in Jest, you can use [`vi.stubEnv`](/api/vi#vi-stubenv) or [`vi.spyOn`](/api/vi#vi-spyon) to do the same also in Vitest. 577 577 578 578 ### Done Callback 579 579 ··· 583 583 584 584 ### Hooks 585 585 586 - `beforeAll`/`beforeEach` hooks may return [teardown function](/api/#setup-and-teardown) in Vitest. Because of that you may need to rewrite your hooks declarations, if they return something other than `undefined` or `null`: 586 + `beforeAll`/`beforeEach` hooks may return [teardown function](/api/hooks#beforeach) in Vitest. Because of that you may need to rewrite your hooks declarations, if they return something other than `undefined` or `null`: 587 587 588 588 ```ts 589 589 beforeEach(() => setActivePinia(createTestingPinia())) // [!code --]
+1 -1
docs/guide/parallelism.md
··· 20 20 21 21 Unlike _test files_, Vitest runs _tests_ in sequence. This means that tests inside a single test file will run in the order they are defined. 22 22 23 - Vitest supports the [`concurrent`](/api/#test-concurrent) option to run tests together. If this option is set, Vitest will group concurrent tests in the same _file_ (the number of simultaneously running tests depends on the [`maxConcurrency`](/config/#maxconcurrency) option) and run them with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). 23 + Vitest supports the [`concurrent`](/api/test#test-concurrent) option to run tests together. If this option is set, Vitest will group concurrent tests in the same _file_ (the number of simultaneously running tests depends on the [`maxConcurrency`](/config/#maxconcurrency) option) and run them with [`Promise.all`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all). 24 24 25 25 Vitest doesn't perform any smart analysis and doesn't create additional workers to run these tests. This means that the performance of your tests will improve only if you rely heavily on asynchronous operations. For example, these tests will still run one after another even though the `concurrent` option is specified. This is because they are synchronous: 26 26
+2 -2
docs/guide/test-context.md
··· 119 119 120 120 #### `onTestFailed` 121 121 122 - The [`onTestFailed`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. 122 + The [`onTestFailed`](/api/hooks#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. 123 123 124 124 #### `onTestFinished` 125 125 126 - The [`onTestFinished`](/api/#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. 126 + The [`onTestFinished`](/api/hooks#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. 127 127 128 128 ## Extend Test Context 129 129
+2
packages/vitest/globals.d.ts
··· 14 14 let afterAll: typeof import('vitest')['afterAll'] 15 15 let beforeEach: typeof import('vitest')['beforeEach'] 16 16 let afterEach: typeof import('vitest')['afterEach'] 17 + let aroundEach: typeof import('vitest')['aroundEach'] 18 + let aroundAll: typeof import('vitest')['aroundAll'] 17 19 let onTestFailed: typeof import('vitest')['onTestFailed'] 18 20 let onTestFinished: typeof import('vitest')['onTestFinished'] 19 21 }
+4
docs/.vitepress/theme/index.ts
··· 25 25 return true 26 26 } 27 27 const url = new URL(to, location.href) 28 + if (url.pathname === '/api/' || url.pathname === '/api' || url.pathname === '/api/index.html') { 29 + setTimeout(() => { router.go(`/api/test`) }) 30 + return false 31 + } 28 32 if (!url.hash) { 29 33 return true 30 34 }
+2 -2
docs/config/browser/expect.md
··· 110 110 111 111 - `testName: string` 112 112 113 - The [`test`](/api/#test)'s name, including parent 114 - [`describe`](/api/#describe), sanitized. 113 + The [`test`](/api/test)'s name, including parent 114 + [`describe`](/api/describe), sanitized. 115 115 116 116 - `attachmentsDir: string` 117 117
+1 -1
docs/guide/browser/trace-view.md
··· 25 25 ``` 26 26 ::: 27 27 28 - By default, Vitest will generate a trace file for each test. You can also configure it to only generate traces on test failures by setting `trace` to `'on-first-retry'`, `'on-all-retries'` or `'retain-on-failure'`. The files will be saved in `__traces__` folder next to your test files. The name of the trace includes the project name, the test name, the [`repeats` count and `retry` count](/api/#test-api-reference): 28 + By default, Vitest will generate a trace file for each test. You can also configure it to only generate traces on test failures by setting `trace` to `'on-first-retry'`, `'on-all-retries'` or `'retain-on-failure'`. The files will be saved in `__traces__` folder next to your test files. The name of the trace includes the project name, the test name, the [`repeats`](/api/test#repeats) count and [`retry`](/api/test#retry) count: 29 29 30 30 ``` 31 31 chromium-my-test-0-0.trace.zip
+12
packages/runner/src/errors.ts
··· 19 19 this.reason = reason 20 20 } 21 21 } 22 + 23 + export class AroundHookSetupError extends Error { 24 + public name = 'AroundHookSetupError' 25 + } 26 + 27 + export class AroundHookTeardownError extends Error { 28 + public name = 'AroundHookTeardownError' 29 + } 30 + 31 + export class AroundHookMultipleCallsError extends Error { 32 + public name = 'AroundHookMultipleCallsError' 33 + }
+28
packages/runner/src/fixture.ts
··· 132 132 cleanupFnArrayMap.delete(context) 133 133 } 134 134 135 + /** 136 + * Returns the current number of cleanup functions registered for the context. 137 + * This can be used as a checkpoint to later clean up only fixtures added after this point. 138 + */ 139 + export function getFixtureCleanupCount(context: object): number { 140 + return cleanupFnArrayMap.get(context)?.length ?? 0 141 + } 142 + 143 + /** 144 + * Cleans up only fixtures that were added after the given checkpoint index. 145 + * This is used by aroundEach to clean up fixtures created inside runTest() 146 + * while preserving fixtures that were created for aroundEach itself. 147 + */ 148 + export async function callFixtureCleanupFrom(context: object, fromIndex: number): Promise<void> { 149 + const cleanupFnArray = cleanupFnArrayMap.get(context) 150 + if (!cleanupFnArray || cleanupFnArray.length <= fromIndex) { 151 + return 152 + } 153 + // Get items added after the checkpoint 154 + const toCleanup = cleanupFnArray.slice(fromIndex) 155 + // Clean up in reverse order 156 + for (const cleanup of toCleanup.reverse()) { 157 + await cleanup() 158 + } 159 + // Remove cleaned up items from the array, keeping items before checkpoint 160 + cleanupFnArray.length = fromIndex 161 + } 162 + 135 163 export function withFixtures(runner: VitestRunner, fn: Function, testContext?: TestContext) { 136 164 return (hookContext?: TestContext): any => { 137 165 const context: (TestContext & { [key: string]: any }) | undefined
+143
packages/runner/src/hooks.ts
··· 1 + import type { VitestRunner } from './types' 1 2 import type { 2 3 AfterAllListener, 3 4 AfterEachListener, 5 + AroundAllListener, 6 + AroundEachListener, 4 7 BeforeAllListener, 5 8 BeforeEachListener, 6 9 OnTestFailedHandler, ··· 21 24 22 25 const CLEANUP_TIMEOUT_KEY = Symbol.for('VITEST_CLEANUP_TIMEOUT') 23 26 const CLEANUP_STACK_TRACE_KEY = Symbol.for('VITEST_CLEANUP_STACK_TRACE') 27 + const AROUND_TIMEOUT_KEY = Symbol.for('VITEST_AROUND_TIMEOUT') 28 + const AROUND_STACK_TRACE_KEY = Symbol.for('VITEST_AROUND_STACK_TRACE') 24 29 25 30 export function getBeforeHookCleanupCallback(hook: Function, result: any, context?: TestContext): Function | undefined { 26 31 if (typeof result === 'function') { ··· 265 270 ) 266 271 }, 267 272 ) 273 + 274 + /** 275 + * Registers a callback function that wraps around all tests within the current suite. 276 + * The callback receives a `runSuite` function that must be called to run the suite's tests. 277 + * This hook is useful for scenarios where you need to wrap an entire suite in a context 278 + * (e.g., starting a server, opening a database connection that all tests share). 279 + * 280 + * **Note:** When multiple `aroundAll` hooks are registered, they are nested inside each other. 281 + * The first registered hook is the outermost wrapper. 282 + * 283 + * **Note:** Unlike `aroundEach`, the `aroundAll` hook does not receive test context or support fixtures, 284 + * as it runs at the suite level before any individual test context is created. 285 + * 286 + * @param {Function} fn - The callback function that wraps the suite. Must call `runSuite()` to run the tests. 287 + * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used. 288 + * @returns {void} 289 + * @example 290 + * ```ts 291 + * // Example of using aroundAll to wrap suite in a tracing span 292 + * aroundAll(async (runSuite) => { 293 + * await tracer.trace('test-suite', runSuite); 294 + * }); 295 + * ``` 296 + * @example 297 + * ```ts 298 + * // Example of using aroundAll with AsyncLocalStorage context 299 + * aroundAll(async (runSuite) => { 300 + * await asyncLocalStorage.run({ suiteId: 'my-suite' }, runSuite); 301 + * }); 302 + * ``` 303 + */ 304 + export function aroundAll( 305 + fn: AroundAllListener, 306 + timeout?: number, 307 + ): void { 308 + assertTypes(fn, '"aroundAll" callback', ['function']) 309 + const stackTraceError = new Error('STACK_TRACE_ERROR') 310 + const resolvedTimeout = timeout ?? getDefaultHookTimeout() 311 + 312 + return getCurrentSuite().on( 313 + 'aroundAll', 314 + Object.assign(fn, { 315 + [AROUND_TIMEOUT_KEY]: resolvedTimeout, 316 + [AROUND_STACK_TRACE_KEY]: stackTraceError, 317 + }), 318 + ) 319 + } 320 + 321 + /** 322 + * Registers a callback function that wraps around each test within the current suite. 323 + * The callback receives a `runTest` function that must be called to run the test. 324 + * This hook is useful for scenarios where you need to wrap tests in a context (e.g., database transactions). 325 + * 326 + * **Note:** When multiple `aroundEach` hooks are registered, they are nested inside each other. 327 + * The first registered hook is the outermost wrapper. 328 + * 329 + * @param {Function} fn - The callback function that wraps the test. Must call `runTest()` to run the test. 330 + * @param {number} [timeout] - Optional timeout in milliseconds for the hook. If not provided, the default hook timeout from the runner's configuration is used. 331 + * @returns {void} 332 + * @example 333 + * ```ts 334 + * // Example of using aroundEach to wrap tests in a database transaction 335 + * aroundEach(async (runTest) => { 336 + * await database.transaction(() => runTest()); 337 + * }); 338 + * ``` 339 + * @example 340 + * ```ts 341 + * // Example of using aroundEach with fixtures 342 + * aroundEach(async (runTest, { db }) => { 343 + * await db.transaction(() => runTest()); 344 + * }); 345 + * ``` 346 + */ 347 + export function aroundEach<ExtraContext = object>( 348 + fn: AroundEachListener<ExtraContext>, 349 + timeout?: number, 350 + ): void { 351 + assertTypes(fn, '"aroundEach" callback', ['function']) 352 + const stackTraceError = new Error('STACK_TRACE_ERROR') 353 + const resolvedTimeout = timeout ?? getDefaultHookTimeout() 354 + const runner = getRunner() 355 + 356 + // Create a wrapper function that supports fixtures in the second argument (context) 357 + // withFixtures resolves fixtures into context, then we call fn with all 3 args 358 + const wrappedFn: AroundEachListener<ExtraContext> = withAroundEachFixtures(runner, fn) 359 + 360 + // Store timeout and stack trace on the function for use in callAroundEachHooks 361 + // Setup and teardown phases will each have their own timeout 362 + return getCurrentSuite<ExtraContext>().on( 363 + 'aroundEach', 364 + Object.assign(wrappedFn, { 365 + [AROUND_TIMEOUT_KEY]: resolvedTimeout, 366 + [AROUND_STACK_TRACE_KEY]: stackTraceError, 367 + }), 368 + ) 369 + } 370 + 371 + /** 372 + * Wraps an aroundEach listener to support fixtures. 373 + * Similar to withFixtures, but handles the aroundEach signature where: 374 + * - First arg is runTest function 375 + * - Second arg is context (where fixtures are destructured from) 376 + * - Third arg is suite 377 + */ 378 + function withAroundEachFixtures<ExtraContext>( 379 + runner: VitestRunner, 380 + fn: AroundEachListener<ExtraContext>, 381 + ): AroundEachListener<ExtraContext> { 382 + // Create the wrapper that will be returned 383 + const wrapper: AroundEachListener<ExtraContext> = (runTest, context, suite) => { 384 + // Create inner function that will be passed to withFixtures 385 + // This function receives context (with fixtures resolved) and calls original fn 386 + const innerFn = (ctx: any) => fn(runTest, ctx, suite) 387 + // Set fixture index to 1 to tell parser to look at second arg of original fn 388 + // Set toString to return original fn string so parser extracts correct params 389 + ;(innerFn as any).__VITEST_FIXTURE_INDEX__ = 1 390 + ;(innerFn as any).toString = () => fn.toString() 391 + 392 + // Use withFixtures to resolve fixtures, passing context as the hook context 393 + const fixtureResolver = withFixtures(runner, innerFn) 394 + return fixtureResolver(context) 395 + } 396 + 397 + return wrapper 398 + } 399 + 400 + export function getAroundHookTimeout(hook: Function): number { 401 + return AROUND_TIMEOUT_KEY in hook && typeof hook[AROUND_TIMEOUT_KEY] === 'number' 402 + ? hook[AROUND_TIMEOUT_KEY] 403 + : getDefaultHookTimeout() 404 + } 405 + 406 + export function getAroundHookStackTrace(hook: Function): Error | undefined { 407 + return AROUND_STACK_TRACE_KEY in hook && hook[AROUND_STACK_TRACE_KEY] instanceof Error 408 + ? hook[AROUND_STACK_TRACE_KEY] 409 + : undefined 410 + } 268 411 269 412 function createTestHook<T>( 270 413 name: string,
+2
packages/runner/src/index.ts
··· 2 2 export { 3 3 afterAll, 4 4 afterEach, 5 + aroundAll, 6 + aroundEach, 5 7 beforeAll, 6 8 beforeEach, 7 9 onTestFailed,
+391 -131
packages/runner/src/run.ts
··· 2 2 import type { DiffOptions } from '@vitest/utils/diff' 3 3 import type { FileSpecification, VitestRunner } from './types/runner' 4 4 import type { 5 + AroundAllListener, 6 + AroundEachListener, 5 7 File, 6 8 SequenceHooks, 7 9 Suite, ··· 21 23 import { getSafeTimers } from '@vitest/utils/timers' 22 24 import { collectTests } from './collect' 23 25 import { abortContextSignal, getFileContext } from './context' 24 - import { PendingError, TestRunAbortError } from './errors' 25 - import { callFixtureCleanup } from './fixture' 26 - import { getBeforeHookCleanupCallback } from './hooks' 26 + import { AroundHookMultipleCallsError, AroundHookSetupError, AroundHookTeardownError, PendingError, TestRunAbortError } from './errors' 27 + import { callFixtureCleanup, callFixtureCleanupFrom, getFixtureCleanupCount } from './fixture' 28 + import { getAroundHookStackTrace, getAroundHookTimeout, getBeforeHookCleanupCallback } from './hooks' 27 29 import { getFn, getHooks } from './map' 28 30 import { addRunningTest, getRunningTests, setCurrentTest } from './test-state' 29 31 import { limitConcurrency } from './utils/limit-concurrency' ··· 217 219 return callbacks 218 220 } 219 221 222 + function getAroundEachHooks(suite: Suite): AroundEachListener[] { 223 + const hooks: AroundEachListener[] = [] 224 + const parentSuite: Suite | null = 'filepath' in suite ? null : suite.suite || suite.file 225 + if (parentSuite) { 226 + hooks.push(...getAroundEachHooks(parentSuite)) 227 + } 228 + hooks.push(...getHooks(suite).aroundEach) 229 + return hooks 230 + } 231 + 232 + function getAroundAllHooks(suite: Suite): AroundAllListener[] { 233 + return getHooks(suite).aroundAll 234 + } 235 + 236 + interface AroundHooksOptions<THook extends Function> { 237 + hooks: THook[] 238 + hookName: 'aroundEach' | 'aroundAll' 239 + callbackName: 'runTest()' | 'runSuite()' 240 + onTimeout?: (error: Error) => void 241 + invokeHook: (hook: THook, use: () => Promise<void>) => Awaitable<unknown> 242 + } 243 + 244 + function makeAroundHookTimeoutError( 245 + hookName: string, 246 + phase: 'setup' | 'teardown', 247 + timeout: number, 248 + stackTraceError?: Error, 249 + ) { 250 + const message = `The ${phase} phase of "${hookName}" hook timed out after ${timeout}ms.` 251 + const ErrorClass = phase === 'setup' ? AroundHookSetupError : AroundHookTeardownError 252 + const error = new ErrorClass(message) 253 + if (stackTraceError?.stack) { 254 + error.stack = stackTraceError.stack.replace(stackTraceError.message, error.message) 255 + } 256 + return error 257 + } 258 + 259 + async function callAroundHooks<THook extends Function>( 260 + runInner: () => Promise<void>, 261 + options: AroundHooksOptions<THook>, 262 + ): Promise<void> { 263 + const { hooks, hookName, callbackName, onTimeout, invokeHook } = options 264 + 265 + if (!hooks.length) { 266 + await runInner() 267 + return 268 + } 269 + 270 + const createTimeoutPromise = ( 271 + timeout: number, 272 + phase: 'setup' | 'teardown', 273 + stackTraceError: Error | undefined, 274 + ): { promise: Promise<never>; clear: () => void } => { 275 + let timer: ReturnType<typeof setTimeout> | undefined 276 + 277 + const promise = new Promise<never>((_, reject) => { 278 + if (timeout > 0 && timeout !== Number.POSITIVE_INFINITY) { 279 + timer = setTimeout(() => { 280 + const error = makeAroundHookTimeoutError(hookName, phase, timeout, stackTraceError) 281 + onTimeout?.(error) 282 + reject(error) 283 + }, timeout) 284 + timer.unref?.() 285 + } 286 + }) 287 + 288 + const clear = () => { 289 + if (timer) { 290 + clearTimeout(timer) 291 + timer = undefined 292 + } 293 + } 294 + 295 + return { promise, clear } 296 + } 297 + 298 + const runNextHook = async (index: number): Promise<void> => { 299 + if (index >= hooks.length) { 300 + return runInner() 301 + } 302 + 303 + const hook = hooks[index] 304 + const timeout = getAroundHookTimeout(hook) 305 + const stackTraceError = getAroundHookStackTrace(hook) 306 + 307 + let useCalled = false 308 + let setupTimeout: { promise: Promise<never>; clear: () => void } 309 + let teardownTimeout: { promise: Promise<never>; clear: () => void } | undefined 310 + 311 + // Promise that resolves when use() is called (setup phase complete) 312 + let resolveUseCalled!: () => void 313 + const useCalledPromise = new Promise<void>((resolve) => { 314 + resolveUseCalled = resolve 315 + }) 316 + 317 + // Promise that resolves when use() returns (inner hooks complete, teardown phase starts) 318 + let resolveUseReturned!: () => void 319 + const useReturnedPromise = new Promise<void>((resolve) => { 320 + resolveUseReturned = resolve 321 + }) 322 + 323 + // Promise that resolves when hook completes 324 + let resolveHookComplete!: () => void 325 + let rejectHookComplete!: (error: Error) => void 326 + const hookCompletePromise = new Promise<void>((resolve, reject) => { 327 + resolveHookComplete = resolve 328 + rejectHookComplete = reject 329 + }) 330 + 331 + const use = async () => { 332 + if (useCalled) { 333 + throw new AroundHookMultipleCallsError( 334 + `The \`${callbackName}\` callback was called multiple times in the \`${hookName}\` hook. ` 335 + + `The callback can only be called once per hook.`, 336 + ) 337 + } 338 + useCalled = true 339 + resolveUseCalled() 340 + 341 + // Setup phase completed - clear setup timer 342 + setupTimeout.clear() 343 + 344 + // Run inner hooks - don't time this against our teardown timeout 345 + await runNextHook(index + 1) 346 + 347 + // Start teardown timer after inner hooks complete - only times this hook's teardown code 348 + teardownTimeout = createTimeoutPromise(timeout, 'teardown', stackTraceError) 349 + 350 + // Signal that use() is returning (teardown phase starting) 351 + resolveUseReturned() 352 + } 353 + 354 + // Start setup timeout 355 + setupTimeout = createTimeoutPromise(timeout, 'setup', stackTraceError) 356 + 357 + // Run the hook in the background 358 + ;(async () => { 359 + try { 360 + await invokeHook(hook, use) 361 + if (!useCalled) { 362 + throw new AroundHookSetupError( 363 + `The \`${callbackName}\` callback was not called in the \`${hookName}\` hook. ` 364 + + `Make sure to call \`${callbackName}\` to run the ${hookName === 'aroundEach' ? 'test' : 'suite'}.`, 365 + ) 366 + } 367 + resolveHookComplete() 368 + } 369 + catch (error) { 370 + rejectHookComplete(error as Error) 371 + } 372 + })() 373 + 374 + // Wait for either: use() to be called OR hook to complete (error) OR setup timeout 375 + try { 376 + await Promise.race([ 377 + useCalledPromise, 378 + hookCompletePromise, 379 + setupTimeout.promise, 380 + ]) 381 + } 382 + finally { 383 + setupTimeout.clear() 384 + } 385 + 386 + // Wait for use() to return (inner hooks complete) OR hook to complete (error during inner hooks) 387 + await Promise.race([ 388 + useReturnedPromise, 389 + hookCompletePromise, 390 + ]) 391 + 392 + // Now teardownTimeout is guaranteed to be set 393 + // Wait for hook to complete (teardown) OR teardown timeout 394 + try { 395 + await Promise.race([ 396 + hookCompletePromise, 397 + teardownTimeout!.promise, 398 + ]) 399 + } 400 + finally { 401 + teardownTimeout!.clear() 402 + } 403 + } 404 + 405 + await runNextHook(0) 406 + } 407 + 408 + async function callAroundAllHooks( 409 + suite: Suite, 410 + runSuiteInner: () => Promise<void>, 411 + ): Promise<void> { 412 + await callAroundHooks(runSuiteInner, { 413 + hooks: getAroundAllHooks(suite), 414 + hookName: 'aroundAll', 415 + callbackName: 'runSuite()', 416 + invokeHook: (hook, use) => hook(use, suite), 417 + }) 418 + } 419 + 420 + async function callAroundEachHooks( 421 + suite: Suite, 422 + test: Test, 423 + runTest: (fixtureCheckpoint: number) => Promise<void>, 424 + ): Promise<void> { 425 + await callAroundHooks( 426 + // Take checkpoint right before runTest - at this point all aroundEach fixtures 427 + // have been resolved, so we can correctly identify which fixtures belong to 428 + // aroundEach (before checkpoint) vs inside runTest (after checkpoint) 429 + () => runTest(getFixtureCleanupCount(test.context)), 430 + { 431 + hooks: getAroundEachHooks(suite), 432 + hookName: 'aroundEach', 433 + callbackName: 'runTest()', 434 + onTimeout: error => abortContextSignal(test.context, error), 435 + invokeHook: (hook, use) => hook(use, test.context, suite), 436 + }, 437 + ) 438 + } 439 + 220 440 const packs = new Map<string, [TaskResult | undefined, TaskMeta]>() 221 441 const eventsPacks: [string, TaskUpdateEvent, undefined][] = [] 222 442 const pendingTasksUpdates: Promise<void>[] = [] ··· 365 585 const retry = getRetryCount(test.retry) 366 586 for (let retryCount = 0; retryCount <= retry; retryCount++) { 367 587 let beforeEachCleanups: unknown[] = [] 368 - try { 369 - await runner.onBeforeTryTask?.(test, { 588 + // fixtureCheckpoint is passed by callAroundEachHooks - it represents the count 589 + // of fixture cleanup functions AFTER all aroundEach fixtures have been resolved 590 + // but BEFORE the test runs. This allows us to clean up only fixtures created 591 + // inside runTest while preserving aroundEach fixtures for teardown. 592 + await callAroundEachHooks(suite, test, async (fixtureCheckpoint) => { 593 + try { 594 + await runner.onBeforeTryTask?.(test, { 595 + retry: retryCount, 596 + repeats: repeatCount, 597 + }) 598 + 599 + test.result!.repeatCount = repeatCount 600 + 601 + beforeEachCleanups = await $('test.beforeEach', () => callSuiteHook( 602 + suite, 603 + test, 604 + 'beforeEach', 605 + runner, 606 + [test.context, suite], 607 + )) 608 + 609 + if (runner.runTask) { 610 + await $('test.callback', () => runner.runTask!(test)) 611 + } 612 + else { 613 + const fn = getFn(test) 614 + if (!fn) { 615 + throw new Error( 616 + 'Test function is not found. Did you add it using `setFn`?', 617 + ) 618 + } 619 + await $('test.callback', () => fn()) 620 + } 621 + 622 + await runner.onAfterTryTask?.(test, { 623 + retry: retryCount, 624 + repeats: repeatCount, 625 + }) 626 + 627 + if (test.result!.state !== 'fail') { 628 + if (!test.repeats) { 629 + test.result!.state = 'pass' 630 + } 631 + else if (test.repeats && retry === retryCount) { 632 + test.result!.state = 'pass' 633 + } 634 + } 635 + } 636 + catch (e) { 637 + failTask(test.result!, e, runner.config.diffOptions) 638 + } 639 + 640 + try { 641 + await runner.onTaskFinished?.(test) 642 + } 643 + catch (e) { 644 + failTask(test.result!, e, runner.config.diffOptions) 645 + } 646 + 647 + try { 648 + await $('test.afterEach', () => callSuiteHook(suite, test, 'afterEach', runner, [ 649 + test.context, 650 + suite, 651 + ])) 652 + if (beforeEachCleanups.length) { 653 + await $('test.cleanup', () => callCleanupHooks(runner, beforeEachCleanups)) 654 + } 655 + // Only clean up fixtures created inside runTest (after the checkpoint) 656 + // Fixtures created for aroundEach will be cleaned up after aroundEach teardown 657 + await callFixtureCleanupFrom(test.context, fixtureCheckpoint) 658 + } 659 + catch (e) { 660 + failTask(test.result!, e, runner.config.diffOptions) 661 + } 662 + 663 + if (test.onFinished?.length) { 664 + await $('test.onFinished', () => callTestHooks(runner, test, test.onFinished!, 'stack')) 665 + } 666 + 667 + if (test.result!.state === 'fail' && test.onFailed?.length) { 668 + await $('test.onFailed', () => callTestHooks( 669 + runner, 670 + test, 671 + test.onFailed!, 672 + runner.config.sequence.hooks, 673 + )) 674 + } 675 + 676 + test.onFailed = undefined 677 + test.onFinished = undefined 678 + 679 + await runner.onAfterRetryTask?.(test, { 370 680 retry: retryCount, 371 681 repeats: repeatCount, 372 682 }) 683 + }).catch((error) => { 684 + failTask(test.result!, error, runner.config.diffOptions) 685 + }) 373 686 374 - test.result.repeatCount = repeatCount 375 - 376 - beforeEachCleanups = await $('test.beforeEach', () => callSuiteHook( 377 - suite, 378 - test, 379 - 'beforeEach', 380 - runner, 381 - [test.context, suite], 382 - )) 383 - 384 - if (runner.runTask) { 385 - await $('test.callback', () => runner.runTask!(test)) 386 - } 387 - else { 388 - const fn = getFn(test) 389 - if (!fn) { 390 - throw new Error( 391 - 'Test function is not found. Did you add it using `setFn`?', 392 - ) 393 - } 394 - await $('test.callback', () => fn()) 395 - } 396 - 397 - await runner.onAfterTryTask?.(test, { 398 - retry: retryCount, 399 - repeats: repeatCount, 400 - }) 401 - 402 - if (test.result.state !== 'fail') { 403 - if (!test.repeats) { 404 - test.result.state = 'pass' 405 - } 406 - else if (test.repeats && retry === retryCount) { 407 - test.result.state = 'pass' 408 - } 409 - } 410 - } 411 - catch (e) { 412 - failTask(test.result, e, runner.config.diffOptions) 413 - } 414 - 687 + // Clean up fixtures that were created for aroundEach (before the checkpoint) 688 + // This runs after aroundEach teardown has completed 415 689 try { 416 - await runner.onTaskFinished?.(test) 417 - } 418 - catch (e) { 419 - failTask(test.result, e, runner.config.diffOptions) 420 - } 421 - 422 - try { 423 - await $('test.afterEach', () => callSuiteHook(suite, test, 'afterEach', runner, [ 424 - test.context, 425 - suite, 426 - ])) 427 - if (beforeEachCleanups.length) { 428 - await $('test.cleanup', () => callCleanupHooks(runner, beforeEachCleanups)) 429 - } 430 690 await callFixtureCleanup(test.context) 431 691 } 432 692 catch (e) { 433 - failTask(test.result, e, runner.config.diffOptions) 693 + failTask(test.result!, e, runner.config.diffOptions) 434 694 } 435 - 436 - if (test.onFinished?.length) { 437 - await $('test.onFinished', () => callTestHooks(runner, test, test.onFinished!, 'stack')) 438 - } 439 - 440 - if (test.result.state === 'fail' && test.onFailed?.length) { 441 - await $('test.onFailed', () => callTestHooks( 442 - runner, 443 - test, 444 - test.onFailed!, 445 - runner.config.sequence.hooks, 446 - )) 447 - } 448 - 449 - test.onFailed = undefined 450 - test.onFinished = undefined 451 - 452 - await runner.onAfterRetryTask?.(test, { 453 - retry: retryCount, 454 - repeats: repeatCount, 455 - }) 456 695 457 696 // skipped with new PendingError 458 697 if (test.result?.pending || test.result?.state === 'skip') { ··· 580 819 updateTask('suite-finished', suite, runner) 581 820 } 582 821 else { 583 - try { 584 - try { 585 - beforeAllCleanups = await $('suite.beforeAll', () => callSuiteHook( 586 - suite, 587 - suite, 588 - 'beforeAll', 589 - runner, 590 - [suite], 591 - )) 592 - } 593 - catch (e) { 594 - markTasksAsSkipped(suite, runner) 595 - throw e 596 - } 822 + let beforeAllError: unknown 823 + let suiteRan = false 597 824 598 - if (runner.runSuite) { 599 - await runner.runSuite(suite) 600 - } 601 - else { 602 - for (let tasksGroup of partitionSuiteChildren(suite)) { 603 - if (tasksGroup[0].concurrent === true) { 604 - await Promise.all(tasksGroup.map(c => runSuiteChild(c, runner))) 825 + try { 826 + await callAroundAllHooks(suite, async () => { 827 + suiteRan = true 828 + try { 829 + // beforeAll 830 + try { 831 + beforeAllCleanups = await $('suite.beforeAll', () => callSuiteHook( 832 + suite, 833 + suite, 834 + 'beforeAll', 835 + runner, 836 + [suite], 837 + )) 838 + } 839 + catch (e) { 840 + beforeAllError = e 841 + failTask(suite.result!, beforeAllError, runner.config.diffOptions) 842 + markTasksAsSkipped(suite, runner) 843 + throw e 844 + } 845 + 846 + // run suite children 847 + if (runner.runSuite) { 848 + await runner.runSuite(suite) 605 849 } 606 850 else { 607 - const { sequence } = runner.config 608 - if (suite.shuffle) { 609 - // run describe block independently from tests 610 - const suites = tasksGroup.filter( 611 - group => group.type === 'suite', 612 - ) 613 - const tests = tasksGroup.filter(group => group.type === 'test') 614 - const groups = shuffle<Task[]>([suites, tests], sequence.seed) 615 - tasksGroup = groups.flatMap(group => 616 - shuffle(group, sequence.seed), 617 - ) 618 - } 619 - for (const c of tasksGroup) { 620 - await runSuiteChild(c, runner) 851 + for (let tasksGroup of partitionSuiteChildren(suite)) { 852 + if (tasksGroup[0].concurrent === true) { 853 + await Promise.all(tasksGroup.map(c => runSuiteChild(c, runner))) 854 + } 855 + else { 856 + const { sequence } = runner.config 857 + if (suite.shuffle) { 858 + // run describe block independently from tests 859 + const suites = tasksGroup.filter( 860 + group => group.type === 'suite', 861 + ) 862 + const tests = tasksGroup.filter(group => group.type === 'test') 863 + const groups = shuffle<Task[]>([suites, tests], sequence.seed) 864 + tasksGroup = groups.flatMap(group => 865 + shuffle(group, sequence.seed), 866 + ) 867 + } 868 + for (const c of tasksGroup) { 869 + await runSuiteChild(c, runner) 870 + } 871 + } 621 872 } 622 873 } 623 874 } 624 - } 875 + finally { 876 + // afterAll runs even if beforeAll or suite children fail 877 + try { 878 + await $('suite.afterAll', () => callSuiteHook(suite, suite, 'afterAll', runner, [suite])) 879 + if (beforeAllCleanups.length) { 880 + await $('suite.cleanup', () => callCleanupHooks(runner, beforeAllCleanups)) 881 + } 882 + if (suite.file === suite) { 883 + const context = getFileContext(suite as File) 884 + await callFixtureCleanup(context) 885 + } 886 + } 887 + catch (e) { 888 + failTask(suite.result!, e, runner.config.diffOptions) 889 + } 890 + } 891 + }) 625 892 } 626 893 catch (e) { 627 - failTask(suite.result, e, runner.config.diffOptions) 628 - } 629 - 630 - try { 631 - await $('suite.afterAll', () => callSuiteHook(suite, suite, 'afterAll', runner, [suite])) 632 - if (beforeAllCleanups.length) { 633 - await $('suite.cleanup', () => callCleanupHooks(runner, beforeAllCleanups)) 894 + // mark tasks as skipped if aroundAll failed before the suite callback was executed 895 + if (!suiteRan) { 896 + markTasksAsSkipped(suite, runner) 634 897 } 635 - if (suite.file === suite) { 636 - const context = getFileContext(suite as File) 637 - await callFixtureCleanup(context) 898 + // don't push beforeAll error again - it was already pushed to preserve the order of beforeAll/afterAll 899 + if (e !== beforeAllError) { 900 + failTask(suite.result!, e, runner.config.diffOptions) 638 901 } 639 - } 640 - catch (e) { 641 - failTask(suite.result, e, runner.config.diffOptions) 642 902 } 643 903 644 904 if (suite.mode === 'run' || suite.mode === 'queued') {
+6 -1
packages/runner/src/suite.ts
··· 35 35 withTimeout, 36 36 } from './context' 37 37 import { mergeContextFixtures, mergeScopedFixtures, withFixtures } from './fixture' 38 - import { afterAll, afterEach, beforeAll, beforeEach } from './hooks' 38 + import { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from './hooks' 39 39 import { getHooks, setFn, setHooks, setTestFixture } from './map' 40 40 import { getCurrentTest } from './test-state' 41 41 import { findTestFileStackTrace } from './utils' ··· 254 254 afterAll: [], 255 255 beforeEach: [], 256 256 afterEach: [], 257 + aroundEach: [], 258 + aroundAll: [], 257 259 } 258 260 } 259 261 ··· 870 872 }, _context) 871 873 } 872 874 875 + taskFn.describe = suite 873 876 taskFn.beforeEach = beforeEach 874 877 taskFn.afterEach = afterEach 875 878 taskFn.beforeAll = beforeAll 876 879 taskFn.afterAll = afterAll 880 + taskFn.aroundEach = aroundEach 881 + taskFn.aroundAll = aroundAll 877 882 878 883 const _test = createChainable( 879 884 ['concurrent', 'sequential', 'skip', 'only', 'todo', 'fails'],
+3
packages/runner/src/types.ts
··· 10 10 export type { 11 11 AfterAllListener, 12 12 AfterEachListener, 13 + AroundAllListener, 14 + AroundEachListener, 13 15 BeforeAllListener, 14 16 BeforeEachListener, 15 17 File, ··· 32 34 SuiteCollector, 33 35 SuiteFactory, 34 36 SuiteHooks, 37 + SuiteOptions, 35 38 Task, 36 39 TaskBase, 37 40 TaskCustomOptions,
+2
packages/vitest/src/constants.ts
··· 36 36 'afterEach', 37 37 'onTestFinished', 38 38 'onTestFailed', 39 + 'aroundEach', 40 + 'aroundAll', 39 41 ]
+1894
test/cli/test/around-each.test.ts
··· 1 + import { expect, test } from 'vitest' 2 + import { runInlineTests } from '../../test-utils' 3 + 4 + function extractLogs(stdout: string): string { 5 + return stdout.split('\n').filter(l => l.includes('>>')).join('\n') 6 + } 7 + 8 + test('basic aroundEach wraps the test', async () => { 9 + const { stdout, stderr } = await runInlineTests({ 10 + 'basic.test.ts': ` 11 + import { aroundEach, test } from 'vitest' 12 + 13 + aroundEach(async (runTest) => { 14 + console.log('>> before test') 15 + await runTest() 16 + console.log('>> after test') 17 + }) 18 + 19 + test('test 1', () => { 20 + console.log('>> inside test') 21 + }) 22 + `, 23 + }) 24 + 25 + expect(stderr).toBe('') 26 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 27 + ">> before test 28 + >> inside test 29 + >> after test" 30 + `) 31 + }) 32 + 33 + test('multiple aroundEach hooks are nested (first is outermost)', async () => { 34 + const { stdout, stderr } = await runInlineTests({ 35 + 'nested-hooks.test.ts': ` 36 + import { aroundEach, test } from 'vitest' 37 + 38 + aroundEach(async (runTest) => { 39 + console.log('>> outer before') 40 + await runTest() 41 + console.log('>> outer after') 42 + }) 43 + 44 + aroundEach(async (runTest) => { 45 + console.log('>> inner before') 46 + await runTest() 47 + console.log('>> inner after') 48 + }) 49 + 50 + test('test 1', () => { 51 + console.log('>> test') 52 + }) 53 + `, 54 + }) 55 + 56 + expect(stderr).toBe('') 57 + 58 + // Extract log lines 59 + const logs = stdout.split('\n').filter(line => line.startsWith('>> ')).map(l => l.trim()) 60 + expect(logs).toEqual([ 61 + '>> outer before', 62 + '>> inner before', 63 + '>> test', 64 + '>> inner after', 65 + '>> outer after', 66 + ]) 67 + }) 68 + 69 + test('aroundEach in nested suites wraps correctly', async () => { 70 + const { stdout, stderr } = await runInlineTests({ 71 + 'nested-suites.test.ts': ` 72 + import { aroundEach, describe, test } from 'vitest' 73 + 74 + aroundEach(async (runTest) => { 75 + console.log('>> root before') 76 + await runTest() 77 + console.log('>> root after') 78 + }) 79 + 80 + describe('suite 1', () => { 81 + aroundEach(async (runTest) => { 82 + console.log('>> suite1 before') 83 + await runTest() 84 + console.log('>> suite1 after') 85 + }) 86 + 87 + test('test in suite 1', () => { 88 + console.log('>> test suite1') 89 + }) 90 + 91 + describe('nested suite', () => { 92 + aroundEach(async (runTest) => { 93 + console.log('>> nested before') 94 + await runTest() 95 + console.log('>> nested after') 96 + }) 97 + 98 + test('test in nested suite', () => { 99 + console.log('>> test nested') 100 + }) 101 + }) 102 + }) 103 + `, 104 + }) 105 + 106 + expect(stderr).toBe('') 107 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 108 + ">> root before 109 + >> suite1 before 110 + >> test suite1 111 + >> suite1 after 112 + >> root after 113 + >> root before 114 + >> suite1 before 115 + >> nested before 116 + >> test nested 117 + >> nested after 118 + >> suite1 after 119 + >> root after" 120 + `) 121 + }) 122 + 123 + test('throws error when runTest is called multiple times', async () => { 124 + const { stderr } = await runInlineTests({ 125 + 'multiple-calls.test.ts': ` 126 + import { aroundEach, test } from 'vitest' 127 + 128 + aroundEach(async (runTest) => { 129 + await runTest() 130 + await runTest() // second call should throw 131 + }) 132 + 133 + test('test 1', () => { 134 + console.log('>> test ran') 135 + }) 136 + `, 137 + }) 138 + 139 + expect(stderr).toMatchInlineSnapshot(` 140 + " 141 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 142 + 143 + FAIL multiple-calls.test.ts > test 1 144 + AroundHookMultipleCallsError: The \`runTest()\` callback was called multiple times in the \`aroundEach\` hook. The callback can only be called once per hook. 145 + ❯ multiple-calls.test.ts:6:15 146 + 4| aroundEach(async (runTest) => { 147 + 5| await runTest() 148 + 6| await runTest() // second call should throw 149 + | ^ 150 + 7| }) 151 + 8| 152 + 153 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 154 + 155 + " 156 + `) 157 + }) 158 + 159 + test('throws error when runTest is not called', async () => { 160 + const { stderr } = await runInlineTests({ 161 + 'no-runtest.test.ts': ` 162 + import { aroundEach, test } from 'vitest' 163 + 164 + aroundEach(async (_runTest) => { 165 + console.log('>> aroundEach without calling runTest') 166 + // Not calling runTest() 167 + }) 168 + 169 + test('test 1', () => { 170 + console.log('>> test should not run') 171 + }) 172 + `, 173 + }) 174 + 175 + expect(stderr).toMatchInlineSnapshot(` 176 + " 177 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 178 + 179 + FAIL no-runtest.test.ts > test 1 180 + AroundHookSetupError: The \`runTest()\` callback was not called in the \`aroundEach\` hook. Make sure to call \`runTest()\` to run the test. 181 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 182 + 183 + " 184 + `) 185 + }) 186 + 187 + test('aroundEach with async operations', async () => { 188 + const { stdout, stderr, errorTree } = await runInlineTests({ 189 + 'async.test.ts': ` 190 + import { aroundEach, test } from 'vitest' 191 + 192 + aroundEach(async (runTest) => { 193 + console.log('>> setup start') 194 + await new Promise(r => setTimeout(r, 10)) 195 + console.log('>> setup done') 196 + await runTest() 197 + console.log('>> cleanup start') 198 + await new Promise(r => setTimeout(r, 10)) 199 + console.log('>> cleanup done') 200 + }) 201 + 202 + test('async test', async () => { 203 + console.log('>> test running') 204 + await new Promise(r => setTimeout(r, 10)) 205 + console.log('>> test done') 206 + }) 207 + `, 208 + }) 209 + 210 + expect(stderr).toBe('') 211 + expect(errorTree()).toMatchInlineSnapshot(` 212 + { 213 + "async.test.ts": { 214 + "async test": "passed", 215 + }, 216 + } 217 + `) 218 + 219 + const logs = stdout.split('\n').filter(line => line.startsWith('>> ')).map(l => l.trim()) 220 + expect(logs).toEqual([ 221 + '>> setup start', 222 + '>> setup done', 223 + '>> test running', 224 + '>> test done', 225 + '>> cleanup start', 226 + '>> cleanup done', 227 + ]) 228 + }) 229 + 230 + test('aroundEach runs for each test', async () => { 231 + const { stdout, stderr } = await runInlineTests({ 232 + 'each-test.test.ts': ` 233 + import { aroundEach, test } from 'vitest' 234 + 235 + let counter = 0 236 + 237 + aroundEach(async (runTest) => { 238 + counter++ 239 + console.log('>> aroundEach run ' + counter) 240 + await runTest() 241 + }) 242 + 243 + test('test 1', () => { 244 + console.log('>> test 1') 245 + }) 246 + 247 + test('test 2', () => { 248 + console.log('>> test 2') 249 + }) 250 + 251 + test('test 3', () => { 252 + console.log('>> test 3') 253 + }) 254 + `, 255 + }) 256 + 257 + expect(stderr).toBe('') 258 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 259 + ">> aroundEach run 1 260 + >> test 1 261 + >> aroundEach run 2 262 + >> test 2 263 + >> aroundEach run 3 264 + >> test 3" 265 + `) 266 + }) 267 + 268 + test('aroundEach with beforeEach and afterEach', async () => { 269 + const { stdout, stderr } = await runInlineTests({ 270 + 'with-hooks.test.ts': ` 271 + import { aroundEach, beforeEach, afterEach, test } from 'vitest' 272 + 273 + beforeEach(() => { 274 + console.log('>> beforeEach') 275 + }) 276 + 277 + aroundEach(async (runTest) => { 278 + console.log('>> aroundEach before') 279 + await runTest() 280 + console.log('>> aroundEach after') 281 + }) 282 + 283 + afterEach(() => { 284 + console.log('>> afterEach') 285 + }) 286 + 287 + test('test 1', () => { 288 + console.log('>> test') 289 + }) 290 + `, 291 + }) 292 + 293 + expect(stderr).toBe('') 294 + 295 + // aroundEach should wrap around beforeEach/test/afterEach 296 + const logs = stdout.split('\n').filter(line => line.startsWith('>> ')).map(l => l.trim()) 297 + expect(logs).toEqual([ 298 + '>> aroundEach before', 299 + '>> beforeEach', 300 + '>> test', 301 + '>> afterEach', 302 + '>> aroundEach after', 303 + ]) 304 + }) 305 + 306 + test('aroundEach receives test context', async () => { 307 + const { stdout, stderr, errorTree } = await runInlineTests({ 308 + 'context.test.ts': ` 309 + import { aroundEach, test, expect } from 'vitest' 310 + 311 + aroundEach(async (runTest, context) => { 312 + console.log('>> test name:', context.task.name) 313 + await runTest() 314 + }) 315 + 316 + test('my test name', () => { 317 + console.log('>> inside test') 318 + }) 319 + `, 320 + }) 321 + 322 + expect(stderr).toBe('') 323 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 324 + ">> test name: my test name 325 + >> inside test" 326 + `) 327 + expect(errorTree()).toMatchInlineSnapshot(` 328 + { 329 + "context.test.ts": { 330 + "my test name": "passed", 331 + }, 332 + } 333 + `) 334 + }) 335 + 336 + test('aroundEach cleanup runs even on test failure', async () => { 337 + const { stdout, stderr, errorTree } = await runInlineTests({ 338 + 'test-failure.test.ts': ` 339 + import { aroundEach, test, expect } from 'vitest' 340 + 341 + aroundEach(async (runTest) => { 342 + console.log('>> setup') 343 + await runTest() 344 + console.log('>> cleanup (should run)') 345 + }) 346 + 347 + test('failing test', () => { 348 + console.log('>> test running') 349 + expect(1).toBe(2) // This will fail 350 + }) 351 + `, 352 + }) 353 + 354 + // Cleanup should still run even when test fails 355 + expect(stderr).toContain('expected 1 to be 2') 356 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 357 + ">> setup 358 + >> test running 359 + >> cleanup (should run)" 360 + `) 361 + expect(errorTree()).toMatchInlineSnapshot(` 362 + { 363 + "test-failure.test.ts": { 364 + "failing test": [ 365 + "expected 1 to be 2 // Object.is equality", 366 + ], 367 + }, 368 + } 369 + `) 370 + }) 371 + 372 + test('aroundEach error prevents test from running', async () => { 373 + const { stdout, stderr } = await runInlineTests({ 374 + 'hook-error.test.ts': ` 375 + import { aroundEach, test } from 'vitest' 376 + 377 + aroundEach(async (runTest) => { 378 + console.log('>> before error') 379 + throw new Error('aroundEach error') 380 + await runTest() // unreachable 381 + }) 382 + 383 + test('test 1', () => { 384 + console.log('>> test should not run') 385 + }) 386 + `, 387 + }) 388 + 389 + expect(stderr).toMatchInlineSnapshot(` 390 + " 391 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 392 + 393 + FAIL hook-error.test.ts > test 1 394 + Error: aroundEach error 395 + ❯ hook-error.test.ts:6:15 396 + 4| aroundEach(async (runTest) => { 397 + 5| console.log('>> before error') 398 + 6| throw new Error('aroundEach error') 399 + | ^ 400 + 7| await runTest() // unreachable 401 + 8| }) 402 + 403 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 404 + 405 + " 406 + `) 407 + expect(extractLogs(stdout)).toMatchInlineSnapshot(`">> before error"`) 408 + }) 409 + 410 + test('aroundEach cleanup error is reported', async () => { 411 + const { stdout, stderr } = await runInlineTests({ 412 + 'cleanup-error.test.ts': ` 413 + import { aroundEach, test } from 'vitest' 414 + 415 + aroundEach(async (runTest) => { 416 + console.log('>> setup') 417 + await runTest() 418 + console.log('>> cleanup before error') 419 + throw new Error('cleanup error') 420 + }) 421 + 422 + test('test 1', () => { 423 + console.log('>> test ran') 424 + }) 425 + `, 426 + }) 427 + 428 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 429 + ">> setup 430 + >> test ran 431 + >> cleanup before error" 432 + `) 433 + expect(stderr).toMatchInlineSnapshot(` 434 + " 435 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 436 + 437 + FAIL cleanup-error.test.ts > test 1 438 + Error: cleanup error 439 + ❯ cleanup-error.test.ts:8:15 440 + 6| await runTest() 441 + 7| console.log('>> cleanup before error') 442 + 8| throw new Error('cleanup error') 443 + | ^ 444 + 9| }) 445 + 10| 446 + 447 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 448 + 449 + " 450 + `) 451 + }) 452 + 453 + test('aroundEach with database transaction pattern', async () => { 454 + const { stderr, errorTree } = await runInlineTests({ 455 + 'transaction.test.ts': ` 456 + import { aroundEach, test, expect } from 'vitest' 457 + 458 + // Simulating a database transaction pattern 459 + const db = { 460 + data: [] as string[], 461 + inTransaction: false, 462 + beginTransaction() { 463 + this.inTransaction = true 464 + console.log('>> BEGIN TRANSACTION') 465 + }, 466 + commit() { 467 + this.inTransaction = false 468 + console.log('>> COMMIT') 469 + }, 470 + rollback() { 471 + this.data = [] 472 + this.inTransaction = false 473 + console.log('>> ROLLBACK') 474 + }, 475 + insert(value: string) { 476 + if (!this.inTransaction) throw new Error('Not in transaction') 477 + this.data.push(value) 478 + console.log('>> INSERT:', value) 479 + } 480 + } 481 + 482 + aroundEach(async (runTest) => { 483 + db.beginTransaction() 484 + try { 485 + await runTest() 486 + } finally { 487 + db.rollback() // Always rollback to keep tests isolated 488 + } 489 + }) 490 + 491 + test('insert data', () => { 492 + db.insert('test1') 493 + expect(db.data).toContain('test1') 494 + }) 495 + 496 + test('data is rolled back', () => { 497 + expect(db.data).toEqual([]) // Previous test's data was rolled back 498 + db.insert('test2') 499 + expect(db.data).toContain('test2') 500 + }) 501 + `, 502 + }) 503 + 504 + expect(stderr).toBe('') 505 + expect(errorTree()).toMatchInlineSnapshot(` 506 + { 507 + "transaction.test.ts": { 508 + "data is rolled back": "passed", 509 + "insert data": "passed", 510 + }, 511 + } 512 + `) 513 + }) 514 + 515 + test('aroundEach with globals: true', async () => { 516 + const { stdout, stderr, errorTree } = await runInlineTests({ 517 + 'globals.test.ts': ` 518 + aroundEach(async (runTest) => { 519 + console.log('>> aroundEach global') 520 + await runTest() 521 + console.log('>> aroundEach global done') 522 + }) 523 + 524 + test('test with globals', () => { 525 + console.log('>> test') 526 + }) 527 + `, 528 + }, { globals: true }) 529 + 530 + expect(stderr).toBe('') 531 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 532 + ">> aroundEach global 533 + >> test 534 + >> aroundEach global done" 535 + `) 536 + expect(errorTree()).toMatchInlineSnapshot(` 537 + { 538 + "globals.test.ts": { 539 + "test with globals": "passed", 540 + }, 541 + } 542 + `) 543 + }) 544 + 545 + test('aroundEach with test.each', async () => { 546 + const { stdout, stderr, errorTree } = await runInlineTests({ 547 + 'test-each.test.ts': ` 548 + import { aroundEach, test } from 'vitest' 549 + 550 + aroundEach(async (runTest, context) => { 551 + console.log('>> aroundEach for:', context.task.name) 552 + await runTest() 553 + }) 554 + 555 + test.each([1, 2, 3])('test %i', (num) => { 556 + console.log('>> test value:', num) 557 + }) 558 + `, 559 + }) 560 + 561 + expect(stderr).toBe('') 562 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 563 + ">> aroundEach for: test 1 564 + >> test value: 1 565 + >> aroundEach for: test 2 566 + >> test value: 2 567 + >> aroundEach for: test 3 568 + >> test value: 3" 569 + `) 570 + expect(errorTree()).toMatchInlineSnapshot(` 571 + { 572 + "test-each.test.ts": { 573 + "test 1": "passed", 574 + "test 2": "passed", 575 + "test 3": "passed", 576 + }, 577 + } 578 + `) 579 + }) 580 + 581 + test('aroundEach with concurrent tests', async () => { 582 + const { stderr, errorTree } = await runInlineTests({ 583 + 'concurrent.test.ts': ` 584 + import { aroundEach, describe, test } from 'vitest' 585 + 586 + const logs: string[] = [] 587 + 588 + aroundEach(async (runTest, context) => { 589 + logs.push('start ' + context.task.name) 590 + await runTest() 591 + logs.push('end ' + context.task.name) 592 + }) 593 + 594 + describe('concurrent suite', { concurrent: true }, () => { 595 + test('test 1', async () => { 596 + await new Promise(r => setTimeout(r, 50)) 597 + }) 598 + 599 + test('test 2', async () => { 600 + await new Promise(r => setTimeout(r, 30)) 601 + }) 602 + 603 + test('test 3', async () => { 604 + await new Promise(r => setTimeout(r, 10)) 605 + }) 606 + }) 607 + `, 608 + }) 609 + 610 + expect(stderr).toBe('') 611 + expect(errorTree()).toMatchInlineSnapshot(` 612 + { 613 + "concurrent.test.ts": { 614 + "concurrent suite": { 615 + "test 1": "passed", 616 + "test 2": "passed", 617 + "test 3": "passed", 618 + }, 619 + }, 620 + } 621 + `) 622 + }) 623 + 624 + test('aroundEach with retry', async () => { 625 + const { stdout, stderr, errorTree } = await runInlineTests({ 626 + 'retry.test.ts': ` 627 + import { aroundEach, test, expect } from 'vitest' 628 + 629 + let attempt = 0 630 + 631 + aroundEach(async (runTest) => { 632 + attempt++ 633 + console.log('>> aroundEach attempt:', attempt) 634 + await runTest() 635 + }) 636 + 637 + test('retried test', { retry: 2 }, () => { 638 + console.log('>> test attempt:', attempt) 639 + if (attempt < 3) { 640 + throw new Error('fail on purpose') 641 + } 642 + }) 643 + `, 644 + }) 645 + 646 + expect(stderr).toBe('') 647 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 648 + ">> aroundEach attempt: 1 649 + >> test attempt: 1 650 + >> aroundEach attempt: 2 651 + >> test attempt: 2 652 + >> aroundEach attempt: 3 653 + >> test attempt: 3" 654 + `) 655 + expect(errorTree()).toMatchInlineSnapshot(` 656 + { 657 + "retry.test.ts": { 658 + "retried test": "passed", 659 + }, 660 + } 661 + `) 662 + }) 663 + 664 + test('aroundEach receives suite as third argument', async () => { 665 + const { stdout, stderr, errorTree } = await runInlineTests({ 666 + 'suite-arg.test.ts': ` 667 + import { aroundEach, describe, test } from 'vitest' 668 + 669 + describe('my suite', () => { 670 + aroundEach(async (runTest, _context, suite) => { 671 + console.log('>> suite name:', suite.name) 672 + await runTest() 673 + }) 674 + 675 + test('test 1', () => { 676 + console.log('>> test') 677 + }) 678 + }) 679 + `, 680 + }) 681 + 682 + expect(stderr).toBe('') 683 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 684 + ">> suite name: my suite 685 + >> test" 686 + `) 687 + expect(errorTree()).toMatchInlineSnapshot(` 688 + { 689 + "suite-arg.test.ts": { 690 + "my suite": { 691 + "test 1": "passed", 692 + }, 693 + }, 694 + } 695 + `) 696 + }) 697 + 698 + test('aroundEach skipped when test is skipped', async () => { 699 + const { stdout, stderr, errorTree } = await runInlineTests({ 700 + 'skipped.test.ts': ` 701 + import { aroundEach, test } from 'vitest' 702 + 703 + aroundEach(async (runTest, context) => { 704 + console.log('>> aroundEach for:', context.task.name) 705 + await runTest() 706 + }) 707 + 708 + test('normal test', () => { 709 + console.log('>> normal test') 710 + }) 711 + 712 + test.skip('skipped test', () => { 713 + console.log('>> skipped test') 714 + }) 715 + `, 716 + }) 717 + 718 + expect(stderr).toBe('') 719 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 720 + ">> aroundEach for: normal test 721 + >> normal test" 722 + `) 723 + expect(errorTree()).toMatchInlineSnapshot(` 724 + { 725 + "skipped.test.ts": { 726 + "normal test": "passed", 727 + "skipped test": "skipped", 728 + }, 729 + } 730 + `) 731 + }) 732 + 733 + test('aroundEach setup phase timeout', async () => { 734 + const { stderr } = await runInlineTests({ 735 + 'setup-timeout.test.ts': ` 736 + import { aroundEach, test } from 'vitest' 737 + 738 + aroundEach(async (runTest) => { 739 + console.log('>> setup start') 740 + // Simulate slow setup 741 + await new Promise(r => setTimeout(r, 5000)) 742 + console.log('>> setup end (should not reach)') 743 + await runTest() 744 + console.log('>> teardown') 745 + }, 100) // 100ms timeout 746 + 747 + test('test with slow setup', () => { 748 + console.log('>> test (should not run)') 749 + }) 750 + `, 751 + }) 752 + 753 + expect(stderr).toMatchInlineSnapshot(` 754 + " 755 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 756 + 757 + FAIL setup-timeout.test.ts > test with slow setup 758 + AroundHookSetupError: The setup phase of "aroundEach" hook timed out after 100ms. 759 + ❯ setup-timeout.test.ts:4:7 760 + 2| import { aroundEach, test } from 'vitest' 761 + 3| 762 + 4| aroundEach(async (runTest) => { 763 + | ^ 764 + 5| console.log('>> setup start') 765 + 6| // Simulate slow setup 766 + 767 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 768 + 769 + " 770 + `) 771 + }) 772 + 773 + test('aroundEach teardown phase timeout', async () => { 774 + const { stdout, stderr } = await runInlineTests({ 775 + 'teardown-timeout.test.ts': ` 776 + import { aroundEach, test } from 'vitest' 777 + 778 + aroundEach(async (runTest) => { 779 + console.log('>> setup') 780 + await runTest() 781 + console.log('>> teardown start') 782 + // Simulate slow teardown 783 + await new Promise(r => setTimeout(r, 5000)) 784 + console.log('>> teardown end (should not reach)') 785 + }, 100) // 100ms timeout 786 + 787 + test('test with slow teardown', () => { 788 + console.log('>> test') 789 + }) 790 + `, 791 + }) 792 + 793 + expect(stderr).toMatchInlineSnapshot(` 794 + " 795 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 796 + 797 + FAIL teardown-timeout.test.ts > test with slow teardown 798 + AroundHookTeardownError: The teardown phase of "aroundEach" hook timed out after 100ms. 799 + ❯ teardown-timeout.test.ts:4:7 800 + 2| import { aroundEach, test } from 'vitest' 801 + 3| 802 + 4| aroundEach(async (runTest) => { 803 + | ^ 804 + 5| console.log('>> setup') 805 + 6| await runTest() 806 + 807 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 808 + 809 + " 810 + `) 811 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 812 + ">> setup 813 + >> test 814 + >> teardown start" 815 + `) 816 + }) 817 + 818 + test('aroundEach setup and teardown have independent timeouts', async () => { 819 + const { stdout, stderr, errorTree } = await runInlineTests({ 820 + 'independent-timeouts.test.ts': ` 821 + import { aroundEach, test } from 'vitest' 822 + 823 + aroundEach(async (runTest) => { 824 + // Setup takes 80ms - under the 100ms timeout 825 + console.log('>> setup start') 826 + await new Promise(r => setTimeout(r, 80)) 827 + console.log('>> setup end') 828 + await runTest() 829 + // Teardown takes 80ms - under the 100ms timeout 830 + console.log('>> teardown start') 831 + await new Promise(r => setTimeout(r, 80)) 832 + console.log('>> teardown end') 833 + }, 100) // 100ms timeout for each phase 834 + 835 + test('test with slow but valid phases', () => { 836 + console.log('>> test') 837 + }) 838 + `, 839 + }) 840 + 841 + // Both phases complete within their individual timeouts 842 + expect(stderr).toBe('') 843 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 844 + ">> setup start 845 + >> setup end 846 + >> test 847 + >> teardown start 848 + >> teardown end" 849 + `) 850 + expect(errorTree()).toMatchInlineSnapshot(` 851 + { 852 + "independent-timeouts.test.ts": { 853 + "test with slow but valid phases": "passed", 854 + }, 855 + } 856 + `) 857 + }) 858 + 859 + test('aroundEach default timeout uses hookTimeout config', async () => { 860 + const { stderr } = await runInlineTests({ 861 + 'default-timeout.test.ts': ` 862 + import { aroundEach, test } from 'vitest' 863 + 864 + aroundEach(async (runTest) => { 865 + // Setup takes longer than hookTimeout (10ms) 866 + await new Promise(r => setTimeout(r, 200)) 867 + await runTest() 868 + }) 869 + 870 + test('test', () => {}) 871 + `, 872 + }, { hookTimeout: 10 }) 873 + 874 + expect(stderr).toMatchInlineSnapshot(` 875 + " 876 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 877 + 878 + FAIL default-timeout.test.ts > test 879 + AroundHookSetupError: The setup phase of "aroundEach" hook timed out after 10ms. 880 + ❯ default-timeout.test.ts:4:7 881 + 2| import { aroundEach, test } from 'vitest' 882 + 3| 883 + 4| aroundEach(async (runTest) => { 884 + | ^ 885 + 5| // Setup takes longer than hookTimeout (10ms) 886 + 6| await new Promise(r => setTimeout(r, 200)) 887 + 888 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 889 + 890 + " 891 + `) 892 + }) 893 + 894 + test('multiple aroundEach hooks with different timeouts', async () => { 895 + const { stdout, stderr, errorTree } = await runInlineTests({ 896 + 'multiple-timeouts.test.ts': ` 897 + import { aroundEach, test } from 'vitest' 898 + 899 + // Outer hook with 200ms timeout 900 + aroundEach(async (runTest) => { 901 + console.log('>> outer setup') 902 + await runTest() 903 + console.log('>> outer teardown') 904 + }, 200) 905 + 906 + // Inner hook with 50ms timeout - this should timeout during setup 907 + aroundEach(async (runTest) => { 908 + console.log('>> inner setup start') 909 + await new Promise(r => setTimeout(r, 100)) // 100ms > 50ms timeout 910 + console.log('>> inner setup end (should not reach)') 911 + await runTest() 912 + console.log('>> inner teardown') 913 + }, 10) 914 + 915 + test('test', () => { 916 + console.log('>> test (should not run)') 917 + }) 918 + `, 919 + }) 920 + 921 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 922 + ">> outer setup 923 + >> inner setup start" 924 + `) 925 + expect(stderr).toMatchInlineSnapshot(` 926 + " 927 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 928 + 929 + FAIL multiple-timeouts.test.ts > test 930 + AroundHookSetupError: The setup phase of "aroundEach" hook timed out after 10ms. 931 + ❯ multiple-timeouts.test.ts:12:7 932 + 10| 933 + 11| // Inner hook with 50ms timeout - this should timeout during set… 934 + 12| aroundEach(async (runTest) => { 935 + | ^ 936 + 13| console.log('>> inner setup start') 937 + 14| await new Promise(r => setTimeout(r, 100)) // 100ms > 50ms tim… 938 + 939 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 940 + 941 + " 942 + `) 943 + expect(errorTree()).toMatchInlineSnapshot(` 944 + { 945 + "multiple-timeouts.test.ts": { 946 + "test": [ 947 + "The setup phase of "aroundEach" hook timed out after 10ms.", 948 + ], 949 + }, 950 + } 951 + `) 952 + }) 953 + 954 + test('multiple aroundEach hooks where inner teardown times out', async () => { 955 + const { stdout, stderr, errorTree } = await runInlineTests({ 956 + 'multiple-teardown-timeout.test.ts': ` 957 + import { aroundEach, test } from 'vitest' 958 + 959 + // Outer hook with 200ms timeout 960 + aroundEach(async (runTest) => { 961 + console.log('>> outer setup') 962 + await runTest() 963 + console.log('>> outer teardown') 964 + }, 200) 965 + 966 + // Inner hook with 50ms timeout - this should timeout during teardown 967 + aroundEach(async (runTest) => { 968 + console.log('>> inner setup') 969 + await runTest() 970 + console.log('>> inner teardown start') 971 + await new Promise(r => setTimeout(r, 100)) // 100ms > 50ms timeout 972 + console.log('>> inner teardown end (should not reach)') 973 + }, 10) 974 + 975 + test('test', () => { 976 + console.log('>> test') 977 + }) 978 + `, 979 + }) 980 + 981 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 982 + ">> outer setup 983 + >> inner setup 984 + >> test 985 + >> inner teardown start" 986 + `) 987 + expect(stderr).toMatchInlineSnapshot(` 988 + " 989 + ⎯⎯⎯⎯⎯⎯⎯ Failed Tests 1 ⎯⎯⎯⎯⎯⎯⎯ 990 + 991 + FAIL multiple-teardown-timeout.test.ts > test 992 + AroundHookTeardownError: The teardown phase of "aroundEach" hook timed out after 10ms. 993 + ❯ multiple-teardown-timeout.test.ts:12:7 994 + 10| 995 + 11| // Inner hook with 50ms timeout - this should timeout during tea… 996 + 12| aroundEach(async (runTest) => { 997 + | ^ 998 + 13| console.log('>> inner setup') 999 + 14| await runTest() 1000 + 1001 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 1002 + 1003 + " 1004 + `) 1005 + expect(errorTree()).toMatchInlineSnapshot(` 1006 + { 1007 + "multiple-teardown-timeout.test.ts": { 1008 + "test": [ 1009 + "The teardown phase of "aroundEach" hook timed out after 10ms.", 1010 + ], 1011 + }, 1012 + } 1013 + `) 1014 + }) 1015 + 1016 + test('aroundEach hook timeouts are independent of each other', async () => { 1017 + const { stdout, stderr, errorTree } = await runInlineTests({ 1018 + 'independent-hook-timeouts.test.ts': ` 1019 + import { aroundEach, test } from 'vitest' 1020 + 1021 + // First hook with short 50ms timeout - but completes quickly 1022 + aroundEach(async (runTest) => { 1023 + console.log('>> first hook setup') 1024 + await runTest() 1025 + console.log('>> first hook teardown') 1026 + }, 10) 1027 + 1028 + // Second hook with 200ms timeout - takes 100ms which is longer than 1029 + // the first hook's 50ms timeout, but within its own 200ms timeout 1030 + aroundEach(async (runTest) => { 1031 + console.log('>> second hook setup start') 1032 + await new Promise(r => setTimeout(r, 100)) 1033 + console.log('>> second hook setup end') 1034 + await runTest() 1035 + console.log('>> second hook teardown start') 1036 + await new Promise(r => setTimeout(r, 100)) 1037 + console.log('>> second hook teardown end') 1038 + }, 200) 1039 + 1040 + test('test', () => { 1041 + console.log('>> test') 1042 + }) 1043 + `, 1044 + }) 1045 + 1046 + expect(stderr).toBe('') 1047 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1048 + ">> first hook setup 1049 + >> second hook setup start 1050 + >> second hook setup end 1051 + >> test 1052 + >> second hook teardown start 1053 + >> second hook teardown end 1054 + >> first hook teardown" 1055 + `) 1056 + expect(errorTree()).toMatchInlineSnapshot(` 1057 + { 1058 + "independent-hook-timeouts.test.ts": { 1059 + "test": "passed", 1060 + }, 1061 + } 1062 + `) 1063 + }) 1064 + 1065 + test('aroundEach with AsyncLocalStorage', async () => { 1066 + const { stdout, stderr, errorTree } = await runInlineTests({ 1067 + 'async-local-storage.test.ts': ` 1068 + import { AsyncLocalStorage } from 'node:async_hooks' 1069 + import { aroundEach, test, expect } from 'vitest' 1070 + 1071 + const requestContext = new AsyncLocalStorage<{ requestId: number }>() 1072 + let requestIdx = 0 1073 + 1074 + aroundEach(async (runTest) => { 1075 + const ctx = { requestId: ++requestIdx } 1076 + console.log('>> setting context:', ctx.requestId) 1077 + await requestContext.run(ctx, runTest) 1078 + console.log('>> context cleared') 1079 + }) 1080 + 1081 + test('first test gets requestId 1', () => { 1082 + const ctx = requestContext.getStore() 1083 + console.log('>> test got context:', ctx?.requestId) 1084 + expect(ctx).toBeDefined() 1085 + expect(ctx?.requestId).toBe(1) 1086 + }) 1087 + 1088 + test('second test gets fresh context with requestId 2', () => { 1089 + const ctx = requestContext.getStore() 1090 + console.log('>> test got context:', ctx?.requestId) 1091 + expect(ctx?.requestId).toBe(2) 1092 + }) 1093 + `, 1094 + }) 1095 + 1096 + expect(stderr).toBe('') 1097 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1098 + ">> setting context: 1 1099 + >> test got context: 1 1100 + >> context cleared 1101 + >> setting context: 2 1102 + >> test got context: 2 1103 + >> context cleared" 1104 + `) 1105 + expect(errorTree()).toMatchInlineSnapshot(` 1106 + { 1107 + "async-local-storage.test.ts": { 1108 + "first test gets requestId 1": "passed", 1109 + "second test gets fresh context with requestId 2": "passed", 1110 + }, 1111 + } 1112 + `) 1113 + }) 1114 + 1115 + test('aroundEach with fixtures', async () => { 1116 + const { stdout, stderr, errorTree } = await runInlineTests({ 1117 + 'fixtures.test.ts': ` 1118 + import { test as base, aroundEach, expect } from 'vitest' 1119 + 1120 + const test = base.extend<{ db: { query: (sql: string) => string } }>({ 1121 + db: async ({}, use) => { 1122 + console.log('>> db fixture setup') 1123 + await use({ 1124 + query: (sql: string) => \`result of: \${sql}\` 1125 + }) 1126 + console.log('>> db fixture teardown') 1127 + }, 1128 + user: async ({}, use) => { 1129 + console.log('>> user fixture setup') 1130 + await use({ name: 'test-user' }) 1131 + console.log('>> user fixture teardown') 1132 + }, 1133 + }) 1134 + 1135 + test.aroundEach(async (runTest, { db }) => { 1136 + console.log('>> aroundEach setup, db available:', !!db) 1137 + const result = db.query('SELECT 1') 1138 + console.log('>> query result:', result) 1139 + await runTest() 1140 + console.log('>> aroundEach teardown') 1141 + }) 1142 + 1143 + test('test with fixture in aroundEach', ({ db, user }) => { 1144 + console.log('>> test running, db available:', !!db) 1145 + expect(db.query('SELECT 2')).toBe('result of: SELECT 2') 1146 + expect(user.name).toBe('test-user') 1147 + }) 1148 + `, 1149 + }) 1150 + 1151 + expect(stderr).toBe('') 1152 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1153 + ">> db fixture setup 1154 + >> aroundEach setup, db available: true 1155 + >> query result: result of: SELECT 1 1156 + >> user fixture setup 1157 + >> test running, db available: true 1158 + >> user fixture teardown 1159 + >> db fixture teardown 1160 + >> aroundEach teardown" 1161 + `) 1162 + expect(errorTree()).toMatchInlineSnapshot(` 1163 + { 1164 + "fixtures.test.ts": { 1165 + "test with fixture in aroundEach": "passed", 1166 + }, 1167 + } 1168 + `) 1169 + }) 1170 + 1171 + test('aroundEach with AsyncLocalStorage fixture and value fixture', async () => { 1172 + const { stdout, stderr, errorTree } = await runInlineTests({ 1173 + 'als-fixtures.test.ts': ` 1174 + import { test as base, aroundEach, expect } from 'vitest' 1175 + import { AsyncLocalStorage } from 'node:async_hooks' 1176 + 1177 + interface RequestContext { 1178 + requestId: number 1179 + } 1180 + 1181 + let requestIdx = 0 1182 + 1183 + const test = base.extend<{ 1184 + requestContext: AsyncLocalStorage<RequestContext> 1185 + currentRequestId: number 1186 + }>({ 1187 + requestContext: async ({}, use) => { 1188 + const als = new AsyncLocalStorage<RequestContext>() 1189 + await use(als) 1190 + }, 1191 + currentRequestId: async ({ requestContext }, use) => { 1192 + const store = requestContext.getStore() 1193 + await use(store?.requestId) 1194 + } 1195 + }) 1196 + 1197 + aroundEach(async (runTest, { requestContext }) => { 1198 + const id = ++requestIdx 1199 + console.log('>> setting context:', id) 1200 + await requestContext.run({ requestId: id }, async () => { 1201 + await runTest() 1202 + }) 1203 + console.log('>> context cleared') 1204 + }) 1205 + 1206 + test('first test gets requestId 1 via fixture', ({ currentRequestId }) => { 1207 + console.log('>> test got requestId:', currentRequestId) 1208 + expect(currentRequestId).toBe(1) 1209 + }) 1210 + 1211 + test('second test gets requestId 2 via fixture', ({ currentRequestId }) => { 1212 + console.log('>> test got requestId:', currentRequestId) 1213 + expect(currentRequestId).toBe(2) 1214 + }) 1215 + `, 1216 + }) 1217 + 1218 + expect(stderr).toBe('') 1219 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1220 + ">> setting context: 1 1221 + >> test got requestId: 1 1222 + >> context cleared 1223 + >> setting context: 2 1224 + >> test got requestId: 2 1225 + >> context cleared" 1226 + `) 1227 + expect(errorTree()).toMatchInlineSnapshot(` 1228 + { 1229 + "als-fixtures.test.ts": { 1230 + "first test gets requestId 1 via fixture": "passed", 1231 + "second test gets requestId 2 via fixture": "passed", 1232 + }, 1233 + } 1234 + `) 1235 + }) 1236 + 1237 + // aroundAll tests 1238 + 1239 + test('basic aroundAll wraps the suite', async () => { 1240 + const { stdout, stderr, errorTree } = await runInlineTests({ 1241 + 'basic.test.ts': ` 1242 + import { test, aroundAll } from 'vitest' 1243 + 1244 + aroundAll(async (runSuite) => { 1245 + console.log('>> aroundAll setup') 1246 + await runSuite() 1247 + console.log('>> aroundAll teardown') 1248 + }) 1249 + 1250 + test('first test', () => { 1251 + console.log('>> first test running') 1252 + }) 1253 + 1254 + test('second test', () => { 1255 + console.log('>> second test running') 1256 + }) 1257 + `, 1258 + }) 1259 + 1260 + expect(stderr).toBe('') 1261 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1262 + ">> aroundAll setup 1263 + >> first test running 1264 + >> second test running 1265 + >> aroundAll teardown" 1266 + `) 1267 + expect(errorTree()).toMatchInlineSnapshot(` 1268 + { 1269 + "basic.test.ts": { 1270 + "first test": "passed", 1271 + "second test": "passed", 1272 + }, 1273 + } 1274 + `) 1275 + }) 1276 + 1277 + test('multiple aroundAll hooks are nested (first is outermost)', async () => { 1278 + const { stdout, stderr, errorTree } = await runInlineTests({ 1279 + 'nested.test.ts': ` 1280 + import { test, aroundAll } from 'vitest' 1281 + 1282 + aroundAll(async (runSuite) => { 1283 + console.log('>> outer setup') 1284 + await runSuite() 1285 + console.log('>> outer teardown') 1286 + }) 1287 + 1288 + aroundAll(async (runSuite) => { 1289 + console.log('>> inner setup') 1290 + await runSuite() 1291 + console.log('>> inner teardown') 1292 + }) 1293 + 1294 + test('test', () => { 1295 + console.log('>> test running') 1296 + }) 1297 + `, 1298 + }) 1299 + 1300 + expect(stderr).toBe('') 1301 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1302 + ">> outer setup 1303 + >> inner setup 1304 + >> test running 1305 + >> inner teardown 1306 + >> outer teardown" 1307 + `) 1308 + expect(errorTree()).toMatchInlineSnapshot(` 1309 + { 1310 + "nested.test.ts": { 1311 + "test": "passed", 1312 + }, 1313 + } 1314 + `) 1315 + }) 1316 + 1317 + test('aroundAll in nested suites wraps correctly', async () => { 1318 + const { stdout, stderr, errorTree } = await runInlineTests({ 1319 + 'nested-suite.test.ts': ` 1320 + import { test, describe, aroundAll } from 'vitest' 1321 + 1322 + aroundAll(async (runSuite) => { 1323 + console.log('>> root aroundAll setup') 1324 + await runSuite() 1325 + console.log('>> root aroundAll teardown') 1326 + }) 1327 + 1328 + test('root test', () => { 1329 + console.log('>> root test running') 1330 + }) 1331 + 1332 + describe('nested suite', () => { 1333 + aroundAll(async (runSuite) => { 1334 + console.log('>> nested aroundAll setup') 1335 + await runSuite() 1336 + console.log('>> nested aroundAll teardown') 1337 + }) 1338 + 1339 + test('nested test', () => { 1340 + console.log('>> nested test running') 1341 + }) 1342 + }) 1343 + `, 1344 + }) 1345 + 1346 + expect(stderr).toBe('') 1347 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1348 + ">> root aroundAll setup 1349 + >> root test running 1350 + >> nested aroundAll setup 1351 + >> nested test running 1352 + >> nested aroundAll teardown 1353 + >> root aroundAll teardown" 1354 + `) 1355 + expect(errorTree()).toMatchInlineSnapshot(` 1356 + { 1357 + "nested-suite.test.ts": { 1358 + "nested suite": { 1359 + "nested test": "passed", 1360 + }, 1361 + "root test": "passed", 1362 + }, 1363 + } 1364 + `) 1365 + }) 1366 + 1367 + test('aroundAll throws error when runSuite is called multiple times', async () => { 1368 + const { stderr } = await runInlineTests({ 1369 + 'multiple-calls.test.ts': ` 1370 + import { test, aroundAll } from 'vitest' 1371 + 1372 + aroundAll(async (runSuite) => { 1373 + await runSuite() 1374 + await runSuite() // second call should throw 1375 + }) 1376 + 1377 + test('test', () => { 1378 + console.log('>> test running') 1379 + }) 1380 + `, 1381 + }) 1382 + 1383 + expect(stderr).toMatchInlineSnapshot(` 1384 + " 1385 + ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ 1386 + 1387 + FAIL multiple-calls.test.ts [ multiple-calls.test.ts ] 1388 + AroundHookMultipleCallsError: The \`runSuite()\` callback was called multiple times in the \`aroundAll\` hook. The callback can only be called once per hook. 1389 + ❯ multiple-calls.test.ts:6:15 1390 + 4| aroundAll(async (runSuite) => { 1391 + 5| await runSuite() 1392 + 6| await runSuite() // second call should throw 1393 + | ^ 1394 + 7| }) 1395 + 8| 1396 + 1397 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 1398 + 1399 + " 1400 + `) 1401 + }) 1402 + 1403 + test('aroundAll throws error when runSuite is not called', async () => { 1404 + const { stderr, errorTree } = await runInlineTests({ 1405 + 'no-run.test.ts': ` 1406 + import { test, aroundAll } from 'vitest' 1407 + 1408 + aroundAll(async (runSuite) => { 1409 + console.log('>> aroundAll setup but not calling runSuite') 1410 + }) 1411 + 1412 + test('test', () => { 1413 + console.log('>> test running') 1414 + }) 1415 + `, 1416 + }) 1417 + 1418 + expect(stderr).toContain('runSuite()') 1419 + expect(errorTree()).toMatchInlineSnapshot(` 1420 + { 1421 + "no-run.test.ts": { 1422 + "test": "skipped", 1423 + }, 1424 + } 1425 + `) 1426 + }) 1427 + 1428 + test('aroundAll cleanup runs even on test failure', async () => { 1429 + const { stdout, errorTree } = await runInlineTests({ 1430 + 'cleanup.test.ts': ` 1431 + import { test, aroundAll, expect } from 'vitest' 1432 + 1433 + aroundAll(async (runSuite) => { 1434 + console.log('>> aroundAll setup') 1435 + await runSuite() 1436 + console.log('>> aroundAll teardown') 1437 + }) 1438 + 1439 + test('failing test', () => { 1440 + console.log('>> failing test running') 1441 + expect(true).toBe(false) 1442 + }) 1443 + `, 1444 + }) 1445 + 1446 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1447 + ">> aroundAll setup 1448 + >> failing test running 1449 + >> aroundAll teardown" 1450 + `) 1451 + expect(errorTree()).toMatchInlineSnapshot(` 1452 + { 1453 + "cleanup.test.ts": { 1454 + "failing test": [ 1455 + "expected true to be false // Object.is equality", 1456 + ], 1457 + }, 1458 + } 1459 + `) 1460 + }) 1461 + 1462 + test('aroundAll with beforeAll and afterAll', async () => { 1463 + const { stdout, stderr, errorTree } = await runInlineTests({ 1464 + 'with-hooks.test.ts': ` 1465 + import { test, beforeAll, afterAll, aroundAll } from 'vitest' 1466 + 1467 + beforeAll(() => { 1468 + console.log('>> beforeAll') 1469 + }) 1470 + 1471 + aroundAll(async (runSuite) => { 1472 + console.log('>> aroundAll setup') 1473 + await runSuite() 1474 + console.log('>> aroundAll teardown') 1475 + }) 1476 + 1477 + afterAll(() => { 1478 + console.log('>> afterAll') 1479 + }) 1480 + 1481 + test('test', () => { 1482 + console.log('>> test running') 1483 + }) 1484 + `, 1485 + }) 1486 + 1487 + expect(stderr).toBe('') 1488 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1489 + ">> aroundAll setup 1490 + >> beforeAll 1491 + >> test running 1492 + >> afterAll 1493 + >> aroundAll teardown" 1494 + `) 1495 + expect(errorTree()).toMatchInlineSnapshot(` 1496 + { 1497 + "with-hooks.test.ts": { 1498 + "test": "passed", 1499 + }, 1500 + } 1501 + `) 1502 + }) 1503 + 1504 + test('aroundAll setup phase timeout', async () => { 1505 + const { stderr, errorTree } = await runInlineTests({ 1506 + 'timeout.test.ts': ` 1507 + import { test, aroundAll } from 'vitest' 1508 + 1509 + aroundAll(async (runSuite) => { 1510 + console.log('>> aroundAll setup starting') 1511 + await new Promise(resolve => setTimeout(resolve, 200)) 1512 + console.log('>> aroundAll setup done') 1513 + await runSuite() 1514 + }, 10) 1515 + 1516 + test('test', () => { 1517 + console.log('>> test running') 1518 + }) 1519 + `, 1520 + }) 1521 + 1522 + expect(stderr).toMatchInlineSnapshot(` 1523 + " 1524 + ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ 1525 + 1526 + FAIL timeout.test.ts [ timeout.test.ts ] 1527 + AroundHookSetupError: The setup phase of "aroundAll" hook timed out after 10ms. 1528 + ❯ timeout.test.ts:4:7 1529 + 2| import { test, aroundAll } from 'vitest' 1530 + 3| 1531 + 4| aroundAll(async (runSuite) => { 1532 + | ^ 1533 + 5| console.log('>> aroundAll setup starting') 1534 + 6| await new Promise(resolve => setTimeout(resolve, 200)) 1535 + 1536 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 1537 + 1538 + " 1539 + `) 1540 + expect(errorTree()).toMatchInlineSnapshot(` 1541 + { 1542 + "timeout.test.ts": { 1543 + "test": "skipped", 1544 + }, 1545 + } 1546 + `) 1547 + }) 1548 + 1549 + test('aroundAll teardown phase timeout', async () => { 1550 + const { stdout, stderr, errorTree } = await runInlineTests({ 1551 + 'teardown-timeout.test.ts': ` 1552 + import { test, aroundAll } from 'vitest' 1553 + 1554 + aroundAll(async (runSuite) => { 1555 + console.log('>> aroundAll setup') 1556 + await runSuite() 1557 + console.log('>> aroundAll teardown starting') 1558 + await new Promise(resolve => setTimeout(resolve, 200)) 1559 + console.log('>> aroundAll teardown done') 1560 + }, 10) 1561 + 1562 + test('test', () => { 1563 + console.log('>> test running') 1564 + }) 1565 + `, 1566 + }) 1567 + 1568 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1569 + ">> aroundAll setup 1570 + >> test running 1571 + >> aroundAll teardown starting" 1572 + `) 1573 + expect(stderr).toMatchInlineSnapshot(` 1574 + " 1575 + ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ 1576 + 1577 + FAIL teardown-timeout.test.ts [ teardown-timeout.test.ts ] 1578 + AroundHookTeardownError: The teardown phase of "aroundAll" hook timed out after 10ms. 1579 + ❯ teardown-timeout.test.ts:4:7 1580 + 2| import { test, aroundAll } from 'vitest' 1581 + 3| 1582 + 4| aroundAll(async (runSuite) => { 1583 + | ^ 1584 + 5| console.log('>> aroundAll setup') 1585 + 6| await runSuite() 1586 + 1587 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 1588 + 1589 + " 1590 + `) 1591 + expect(errorTree()).toMatchInlineSnapshot(` 1592 + { 1593 + "teardown-timeout.test.ts": { 1594 + "test": "passed", 1595 + }, 1596 + } 1597 + `) 1598 + }) 1599 + 1600 + test('aroundAll receives suite as second argument', async () => { 1601 + const { stdout, stderr, errorTree } = await runInlineTests({ 1602 + 'suite-arg.test.ts': ` 1603 + import { test, describe, aroundAll } from 'vitest' 1604 + 1605 + describe('my suite', () => { 1606 + aroundAll(async (runSuite, suite) => { 1607 + console.log('>> suite name:', suite.name) 1608 + await runSuite() 1609 + }) 1610 + 1611 + test('test', () => { 1612 + console.log('>> test running') 1613 + }) 1614 + }) 1615 + `, 1616 + }) 1617 + 1618 + expect(stderr).toBe('') 1619 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1620 + ">> suite name: my suite 1621 + >> test running" 1622 + `) 1623 + expect(errorTree()).toMatchInlineSnapshot(` 1624 + { 1625 + "suite-arg.test.ts": { 1626 + "my suite": { 1627 + "test": "passed", 1628 + }, 1629 + }, 1630 + } 1631 + `) 1632 + }) 1633 + 1634 + test('aroundAll with server start/stop pattern', async () => { 1635 + const { stdout, stderr, errorTree } = await runInlineTests({ 1636 + 'server.test.ts': ` 1637 + import { test, aroundAll, expect } from 'vitest' 1638 + 1639 + let serverPort: number | null = null 1640 + 1641 + aroundAll(async (runSuite) => { 1642 + // Simulate server start 1643 + serverPort = 3000 1644 + console.log('>> server started on port', serverPort) 1645 + await runSuite() 1646 + // Simulate server stop 1647 + console.log('>> server stopping') 1648 + serverPort = null 1649 + }) 1650 + 1651 + test('first request', () => { 1652 + console.log('>> making request to port', serverPort) 1653 + expect(serverPort).toBe(3000) 1654 + }) 1655 + 1656 + test('second request', () => { 1657 + console.log('>> making request to port', serverPort) 1658 + expect(serverPort).toBe(3000) 1659 + }) 1660 + `, 1661 + }) 1662 + 1663 + expect(stderr).toBe('') 1664 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1665 + ">> server started on port 3000 1666 + >> making request to port 3000 1667 + >> making request to port 3000 1668 + >> server stopping" 1669 + `) 1670 + expect(errorTree()).toMatchInlineSnapshot(` 1671 + { 1672 + "server.test.ts": { 1673 + "first request": "passed", 1674 + "second request": "passed", 1675 + }, 1676 + } 1677 + `) 1678 + }) 1679 + 1680 + test('aroundAll with multiple suites and multiple hooks in same suite', async () => { 1681 + const { stdout, stderr, errorTree } = await runInlineTests({ 1682 + 'multi-suite.test.ts': ` 1683 + import { test, describe, aroundAll } from 'vitest' 1684 + 1685 + aroundAll(async (runSuite) => { 1686 + console.log('>> root aroundAll 1 setup') 1687 + await runSuite() 1688 + console.log('>> root aroundAll 1 teardown') 1689 + }) 1690 + 1691 + aroundAll(async (runSuite) => { 1692 + console.log('>> root aroundAll 2 setup') 1693 + await runSuite() 1694 + console.log('>> root aroundAll 2 teardown') 1695 + }) 1696 + 1697 + test('root test', () => { 1698 + console.log('>> root test') 1699 + }) 1700 + 1701 + describe('suite A', () => { 1702 + aroundAll(async (runSuite) => { 1703 + console.log('>> suite A aroundAll 1 setup') 1704 + await runSuite() 1705 + console.log('>> suite A aroundAll 1 teardown') 1706 + }) 1707 + 1708 + aroundAll(async (runSuite) => { 1709 + console.log('>> suite A aroundAll 2 setup') 1710 + await runSuite() 1711 + console.log('>> suite A aroundAll 2 teardown') 1712 + }) 1713 + 1714 + test('test A1', () => { 1715 + console.log('>> test A1') 1716 + }) 1717 + 1718 + test('test A2', () => { 1719 + console.log('>> test A2') 1720 + }) 1721 + }) 1722 + 1723 + describe('suite B', () => { 1724 + aroundAll(async (runSuite) => { 1725 + console.log('>> suite B aroundAll setup') 1726 + await runSuite() 1727 + console.log('>> suite B aroundAll teardown') 1728 + }) 1729 + 1730 + test('test B1', () => { 1731 + console.log('>> test B1') 1732 + }) 1733 + 1734 + describe('nested suite', () => { 1735 + aroundAll(async (runSuite) => { 1736 + console.log('>> nested aroundAll setup') 1737 + await runSuite() 1738 + console.log('>> nested aroundAll teardown') 1739 + }) 1740 + 1741 + test('nested test', () => { 1742 + console.log('>> nested test') 1743 + }) 1744 + }) 1745 + }) 1746 + `, 1747 + }) 1748 + 1749 + expect(stderr).toBe('') 1750 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1751 + ">> root aroundAll 1 setup 1752 + >> root aroundAll 2 setup 1753 + >> root test 1754 + >> suite A aroundAll 1 setup 1755 + >> suite A aroundAll 2 setup 1756 + >> test A1 1757 + >> test A2 1758 + >> suite A aroundAll 2 teardown 1759 + >> suite A aroundAll 1 teardown 1760 + >> suite B aroundAll setup 1761 + >> test B1 1762 + >> nested aroundAll setup 1763 + >> nested test 1764 + >> nested aroundAll teardown 1765 + >> suite B aroundAll teardown 1766 + >> root aroundAll 2 teardown 1767 + >> root aroundAll 1 teardown" 1768 + `) 1769 + expect(errorTree()).toMatchInlineSnapshot(` 1770 + { 1771 + "multi-suite.test.ts": { 1772 + "root test": "passed", 1773 + "suite A": { 1774 + "test A1": "passed", 1775 + "test A2": "passed", 1776 + }, 1777 + "suite B": { 1778 + "nested suite": { 1779 + "nested test": "passed", 1780 + }, 1781 + "test B1": "passed", 1782 + }, 1783 + }, 1784 + } 1785 + `) 1786 + }) 1787 + 1788 + test('aroundAll with module-level AsyncLocalStorage and test fixture', async () => { 1789 + const { stdout, stderr, errorTree } = await runInlineTests({ 1790 + 'module-als.test.ts': ` 1791 + import { test as base, aroundAll, expect } from 'vitest' 1792 + import { AsyncLocalStorage } from 'node:async_hooks' 1793 + 1794 + interface RequestContext { 1795 + requestId: number 1796 + } 1797 + 1798 + // Module-level AsyncLocalStorage shared between aroundAll and fixtures 1799 + const requestContext = new AsyncLocalStorage<RequestContext>() 1800 + let suiteRequestId = 0 1801 + 1802 + const test = base.extend<{ 1803 + currentRequestId: number 1804 + }>({ 1805 + currentRequestId: async ({}, use) => { 1806 + const store = requestContext.getStore() 1807 + console.log('>> currentRequestId fixture reading store:', store?.requestId) 1808 + await use(store?.requestId) 1809 + } 1810 + }) 1811 + 1812 + aroundAll(async (runSuite) => { 1813 + suiteRequestId++ 1814 + console.log('>> aroundAll setup, setting requestId:', suiteRequestId) 1815 + await requestContext.run({ requestId: suiteRequestId }, async () => { 1816 + await runSuite() 1817 + }) 1818 + console.log('>> aroundAll teardown') 1819 + }) 1820 + 1821 + test('first test gets requestId from aroundAll context', ({ currentRequestId }) => { 1822 + console.log('>> first test, currentRequestId:', currentRequestId) 1823 + expect(currentRequestId).toBe(1) 1824 + }) 1825 + 1826 + test('second test gets same requestId from aroundAll context', ({ currentRequestId }) => { 1827 + console.log('>> second test, currentRequestId:', currentRequestId) 1828 + expect(currentRequestId).toBe(1) 1829 + }) 1830 + `, 1831 + }) 1832 + 1833 + expect(stderr).toBe('') 1834 + expect(extractLogs(stdout)).toMatchInlineSnapshot(` 1835 + ">> aroundAll setup, setting requestId: 1 1836 + >> currentRequestId fixture reading store: 1 1837 + >> first test, currentRequestId: 1 1838 + >> currentRequestId fixture reading store: 1 1839 + >> second test, currentRequestId: 1 1840 + >> aroundAll teardown" 1841 + `) 1842 + expect(errorTree()).toMatchInlineSnapshot(` 1843 + { 1844 + "module-als.test.ts": { 1845 + "first test gets requestId from aroundAll context": "passed", 1846 + "second test gets same requestId from aroundAll context": "passed", 1847 + }, 1848 + } 1849 + `) 1850 + }) 1851 + 1852 + test('tests are skipped when aroundAll setup fails', async () => { 1853 + const { stderr, errorTree } = await runInlineTests({ 1854 + 'aroundAll-setup-error.test.ts': ` 1855 + import { test, aroundAll } from 'vitest' 1856 + 1857 + aroundAll(async () => { 1858 + throw new Error('aroundAll setup error') 1859 + }) 1860 + 1861 + test('test should be skipped', () => { 1862 + console.log('>> test should not run') 1863 + }) 1864 + `, 1865 + }) 1866 + 1867 + expect(stderr).toMatchInlineSnapshot(` 1868 + " 1869 + ⎯⎯⎯⎯⎯⎯ Failed Suites 1 ⎯⎯⎯⎯⎯⎯⎯ 1870 + 1871 + FAIL aroundAll-setup-error.test.ts [ aroundAll-setup-error.test.ts ] 1872 + Error: aroundAll setup error 1873 + ❯ aroundAll-setup-error.test.ts:5:15 1874 + 3| 1875 + 4| aroundAll(async () => { 1876 + 5| throw new Error('aroundAll setup error') 1877 + | ^ 1878 + 6| }) 1879 + 7| 1880 + 1881 + ⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯[1/1]⎯ 1882 + 1883 + " 1884 + `) 1885 + 1886 + // Test should be skipped because aroundAll setup failed 1887 + expect(errorTree()).toMatchInlineSnapshot(` 1888 + { 1889 + "aroundAll-setup-error.test.ts": { 1890 + "test should be skipped": "skipped", 1891 + }, 1892 + } 1893 + `) 1894 + })
+2 -1
test/cli/test/expect-task.test.ts
··· 173 173 test: testBoundLocalExtend, 174 174 }, 175 175 ] as const)('works with $name', async ({ options, test }, { expect }) => { 176 - const { stdout } = await runInlineTests( 176 + const { stdout, stderr } = await runInlineTests( 177 177 { 178 178 'basic.test.ts': test, 179 179 'to-match-test.ts': toMatchTest, ··· 181 181 { reporters: ['tap'], ...options }, 182 182 ) 183 183 184 + expect(stderr).toBe('') 184 185 expect(stdout.replace(/[\d.]+ms/g, '<time>')).toMatchInlineSnapshot(` 185 186 "TAP version 13 186 187 1..1
+2
test/core/test/exports.test.ts
··· 22 22 "TestRunner": "function", 23 23 "afterAll": "function", 24 24 "afterEach": "function", 25 + "aroundAll": "function", 26 + "aroundEach": "function", 25 27 "assert": "function", 26 28 "assertType": "function", 27 29 "beforeAll": "function",
+21 -1
packages/runner/src/types/tasks.ts
··· 1 1 import type { Awaitable, TestError } from '@vitest/utils' 2 2 import type { FixtureItem } from '../fixture' 3 - import type { afterAll, afterEach, beforeAll, beforeEach } from '../hooks' 3 + import type { afterAll, afterEach, aroundAll, aroundEach, beforeAll, beforeEach } from '../hooks' 4 4 import type { ChainableFunction } from '../utils/chain' 5 5 6 6 export type RunMode = 'run' | 'skip' | 'only' | 'todo' | 'queued' ··· 591 591 afterAll: typeof afterAll 592 592 beforeEach: typeof beforeEach<ExtraContext> 593 593 afterEach: typeof afterEach<ExtraContext> 594 + aroundEach: typeof aroundEach<ExtraContext> 595 + aroundAll: typeof aroundAll 594 596 } 595 597 596 598 export type TestAPI<ExtraContext = object> = ChainableTestAPI<ExtraContext> ··· 607 609 scoped: ( 608 610 fixtures: Partial<Fixtures<ExtraContext>>, 609 611 ) => void 612 + describe: SuiteAPI<ExtraContext> 610 613 } 611 614 612 615 export interface FixtureOptions { ··· 703 706 ): Awaitable<unknown> 704 707 } 705 708 709 + export interface AroundEachListener<ExtraContext = object> { 710 + ( 711 + runTest: () => Promise<void>, 712 + context: TestContext & ExtraContext, 713 + suite: Readonly<Suite> 714 + ): Awaitable<unknown> 715 + } 716 + 717 + export interface AroundAllListener { 718 + ( 719 + runSuite: () => Promise<void>, 720 + suite: Readonly<Suite | File> 721 + ): Awaitable<unknown> 722 + } 723 + 706 724 export interface SuiteHooks<ExtraContext = object> { 707 725 beforeAll: BeforeAllListener[] 708 726 afterAll: AfterAllListener[] 709 727 beforeEach: BeforeEachListener<ExtraContext>[] 710 728 afterEach: AfterEachListener<ExtraContext>[] 729 + aroundEach: AroundEachListener<ExtraContext>[] 730 + aroundAll: AroundAllListener[] 711 731 } 712 732 713 733 export interface TaskCustomOptions extends TestOptions {
+3
packages/vitest/src/public/index.ts
··· 100 100 export { 101 101 afterAll, 102 102 afterEach, 103 + aroundAll, 104 + aroundEach, 103 105 beforeAll, 104 106 beforeEach, 105 107 describe, ··· 126 128 SuiteAPI, 127 129 SuiteCollector, 128 130 SuiteFactory, 131 + SuiteOptions, 129 132 TaskCustomOptions, 130 133 TaskMeta, 131 134 TaskState,