[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.

docs: improve learning section (#10140)

authored by

Vladimir and committed by
GitHub
(Apr 14, 2026, 2:44 PM +0200) 9a288af6 9423dc08

+243 -46
+39 -18
docs/guide/learn/async.md
··· 49 49 Don't forget the `await` before `expect`. Vitest will detect unawaited assertions and print a warning at the end of the test, but it's best to always include `await` explicitly. Vitest will also wait for all pending promises in `Promise.all` before starting the next test, but relying on this behavior makes tests harder to understand. 50 50 ::: 51 51 52 + ## Assertion Counting 53 + 54 + With async code, there's a subtle risk: an assertion inside a callback or `.then()` chain might never execute, and the test would still pass because no assertion failed. [`expect.hasAssertions()`](/api/expect#hasassertions) guards against this by verifying that at least one assertion ran during the test: 55 + 56 + ```js 57 + test('callback is invoked', async () => { 58 + expect.hasAssertions() 59 + 60 + const data = await fetchData() 61 + data.items.forEach((item) => { 62 + expect(item.id).toBeDefined() 63 + }) 64 + // if data.items is empty, the test fails instead of silently passing 65 + }) 66 + ``` 67 + 68 + When you know exactly how many assertions should run, [`expect.assertions(n)`](/api/expect#assertions) is more precise: 69 + 70 + ```js 71 + test('both callbacks are called', async () => { 72 + expect.assertions(2) 73 + 74 + await Promise.all([ 75 + fetchUser(1).then(user => expect(user.name).toBe('Alice')), 76 + fetchUser(2).then(user => expect(user.name).toBe('Bob')), 77 + ]) 78 + }) 79 + ``` 80 + 81 + In most cases, `async`/`await` with direct assertions is clear enough and you don't need assertion counting. It's most useful when assertions are inside callbacks, loops, or conditional branches where you want to guarantee they actually executed. 82 + 83 + ::: tip 84 + If you want every test in your project to require at least one assertion, enable [`expect.requireAssertions`](/config/expect#expect-requireassertions) in your config instead of adding `expect.hasAssertions()` to each test manually. 85 + ::: 86 + 52 87 ## Callbacks 53 88 54 89 Some older APIs use callbacks instead of promises. Since Vitest works with promises, the simplest approach is to wrap the callback in a `Promise`: ··· 68 103 69 104 This pattern works for any callback-based API. Pass `resolve` as the success callback, and the test will wait until the callback is invoked. 70 105 106 + ::: tip 107 + Most modern Node.js APIs (such as `fs/promises` and `fetch`) support promises natively, so you can use `async`/`await` directly. The callback wrapping pattern above is mainly useful for older libraries that haven't adopted promises yet. 108 + ::: 109 + 71 110 ## Timeouts 72 111 73 112 By default, each test has a 5-second timeout. If a test takes longer than that (perhaps because a promise never resolves, or a network request hangs), it will fail with a timeout error. This prevents your test suite from getting stuck indefinitely. ··· 91 130 }, 92 131 }) 93 132 ``` 94 - 95 - ## Concurrent Tests 96 - 97 - By default, tests within a file run one after another. This is usually what you want, especially when tests share setup code. But if you have many independent async tests that each spend most of their time waiting (on network, disk, timers, etc.), running them concurrently with [`test.concurrent`](/api/test#concurrent) can significantly speed things up: 98 - 99 - ```js 100 - test.concurrent('first async test', async () => { 101 - const result = await fetchUser(1) 102 - expect(result.name).toBe('Alice') 103 - }) 104 - 105 - test.concurrent('second async test', async () => { 106 - const result = await fetchUser(2) 107 - expect(result.name).toBe('Bob') 108 - }) 109 - ``` 110 - 111 - See the [Parallelism](/guide/parallelism) guide for the full picture of how Vitest runs tests in parallel, both across files and within them. 112 133 113 134 ## Unhandled Rejections 114 135
+13 -1
docs/guide/learn/debugging-tests.md
··· 53 53 }) 54 54 ``` 55 55 56 + If you have many failures and want to focus on the first one, use [`--bail`](/config/bail) to stop after a set number of failures: 57 + 58 + ```bash 59 + vitest --bail 1 60 + ``` 61 + 56 62 If the test passes when run alone but fails when run with others, you have a test isolation problem (more on that below). If it fails even when run alone, the issue is in the test itself or the code it's testing. 57 63 58 64 ## Common Causes of Failures ··· 188 194 189 195 ### Attaching a Debugger 190 196 191 - For more complex issues where you need to step through code line by line, you can attach a debugger. See the [Debugging](/guide/debugging) guide for setup instructions for VS Code, IntelliJ, and Chrome DevTools. 197 + For more complex issues where you need to step through code line by line, you can run Vitest with the `--inspect-brk` flag and attach a debugger. The `--no-file-parallelism` flag ensures tests run in the main thread so breakpoints work reliably: 198 + 199 + ```bash 200 + vitest --inspect-brk --no-file-parallelism 201 + ``` 202 + 203 + Then attach from VS Code, IntelliJ, or Chrome DevTools (`chrome://inspect`). See the [Debugging](/guide/debugging) guide for detailed setup instructions for each editor. 192 204 193 205 ## Getting Help 194 206
+47 -1
docs/guide/learn/matchers.md
··· 50 50 }) 51 51 ``` 52 52 53 + There's also [`toStrictEqual`](/api/expect#tostrictequal), which is stricter than `toEqual` in three ways: it checks `undefined` properties, distinguishes sparse arrays from `undefined` values, and verifies that objects have the same type (not just the same shape): 54 + 55 + ```js 56 + test('toEqual vs toStrictEqual', () => { 57 + // toEqual ignores undefined properties 58 + expect({ a: 1 }).toEqual({ a: 1, b: undefined }) 59 + 60 + // toStrictEqual catches them 61 + expect({ a: 1 }).not.toStrictEqual({ a: 1, b: undefined }) 62 + 63 + // toEqual doesn't check object types 64 + class User { 65 + constructor(name) { 66 + this.name = name 67 + } 68 + } 69 + expect(new User('Alice')).toEqual({ name: 'Alice' }) 70 + expect(new User('Alice')).not.toStrictEqual({ name: 'Alice' }) 71 + }) 72 + ``` 73 + 53 74 ::: tip 54 - A good rule of thumb: use `toBe` for primitives (numbers, strings, booleans) and `toEqual` for objects and arrays. 75 + A good rule of thumb: use `toBe` for primitives (numbers, strings, booleans), `toEqual` for comparing structure, and `toStrictEqual` when you also care about types and explicit `undefined` values. 55 76 ::: 56 77 57 78 You can also negate any matcher by inserting `.not` before it. This is useful when you want to verify that something is *not* the case: ··· 192 213 expect(user).toHaveProperty('address.zip') 193 214 }) 194 215 ``` 216 + 217 + ## Asymmetric Matchers 218 + 219 + Sometimes you don't know the exact value, but you know its type or shape. Asymmetric matchers let you describe what a value should *look like* without pinning down the exact content. They work inside any matcher that does deep comparison, like `toEqual` or `toMatchObject`: 220 + 221 + ```js 222 + test('user has the right shape', () => { 223 + const user = createUser('Alice') 224 + 225 + expect(user).toEqual({ 226 + id: expect.any(Number), 227 + name: 'Alice', 228 + email: expect.stringContaining('@'), 229 + roles: expect.arrayContaining(['viewer']), 230 + }) 231 + }) 232 + ``` 233 + 234 + The most common asymmetric matchers are: 235 + 236 + - [`expect.any(Constructor)`](/api/expect#expect-any) matches any value created with the given constructor (e.g., `Number`, `String`, `Array`) 237 + - [`expect.stringContaining(str)`](/api/expect#expect-stringcontaining) matches a string that includes the given substring 238 + - [`expect.stringMatching(regex)`](/api/expect#expect-stringmatching) matches a string against a regular expression 239 + - [`expect.arrayContaining(arr)`](/api/expect#expect-arraycontaining) matches an array that includes all items in the expected array (order doesn't matter, extra items are allowed) 240 + - [`expect.objectContaining(obj)`](/api/expect#expect-objectcontaining) matches an object that includes at least the specified properties 195 241 196 242 ## Exceptions 197 243
+6 -2
docs/guide/learn/mock-functions.md
··· 114 114 expect(greet).toHaveBeenCalledWith('Alice') 115 115 expect(greet).toHaveBeenCalledWith('Bob', 'Charlie') 116 116 117 + // Check the arguments of a specific call by position 118 + expect(greet).toHaveBeenNthCalledWith(1, 'Alice') 119 + expect(greet).toHaveBeenLastCalledWith('Bob', 'Charlie') 120 + 117 121 // Access the raw call data 118 122 expect(greet.mock.calls).toEqual([ 119 123 ['Alice'], ··· 267 271 [`vi.mock`](/api/vi#vi-mock) calls are hoisted to the top of the file. They run before any imports. This means the mocked version is in place by the time your test code runs. 268 272 ::: 269 273 270 - ::: tip 271 - Notice that we pass `import('./db.js')` instead of a plain string `'./db.js'`. When you use `import()`, TypeScript can infer the module's types, so the factory function's return value is type-checked and `importOriginal` returns the correctly typed module. As a bonus, if you rename or move the file in your IDE, the import path will be updated automatically. If you use a string, you lose both the type safety and the automatic refactoring. 274 + ::: warning 275 + Always pass `import('./db.js')` rather than a plain string `'./db.js'`. When you use `import()`, TypeScript can infer the module's types, so the factory function's return value is type-checked and `importOriginal` returns the correctly typed module. As a bonus, if you rename or move the file in your IDE, the import path will be updated automatically. If you use a string, you lose both the type safety and the automatic refactoring. 272 276 ::: 273 277 274 278 Vitest has comprehensive guides for specific mocking scenarios:
+19 -10
docs/guide/learn/setup-teardown.md
··· 121 121 import { afterAll, afterEach, beforeAll, beforeEach, describe, test } from 'vitest' 122 122 123 123 beforeAll(() => console.log('1 - beforeAll')) 124 - afterAll(() => console.log('6 - afterAll')) 124 + afterAll(() => console.log('8 - afterAll')) 125 125 beforeEach(() => console.log('2 - beforeEach')) 126 - afterEach(() => console.log('4 - afterEach')) 126 + afterEach(() => console.log('5 - afterEach')) 127 127 128 128 describe('suite', () => { 129 129 beforeEach(() => console.log('3 - inner beforeEach')) 130 - afterEach(() => console.log('3.5 - inner afterEach')) 130 + afterEach(() => console.log('4 - inner afterEach')) 131 + 132 + test('first test', () => { 133 + console.log(' first test') 134 + }) 131 135 132 - test('example', () => { 133 - console.log(' test') 136 + test('second test', () => { 137 + console.log(' second test') 134 138 }) 135 139 }) 136 140 ``` ··· 141 145 1 - beforeAll 142 146 2 - beforeEach 143 147 3 - inner beforeEach 144 - test 145 - 3.5 - inner afterEach 146 - 4 - afterEach 147 - 6 - afterAll 148 + first test 149 + 4 - inner afterEach 150 + 5 - afterEach 151 + 2 - beforeEach 152 + 3 - inner beforeEach 153 + second test 154 + 4 - inner afterEach 155 + 5 - afterEach 156 + 8 - afterAll 148 157 ``` 149 158 150 - Notice the pattern: outer `beforeEach` runs first (setting up the broadest context), then inner `beforeEach` runs (narrowing the context). After the test, the order reverses: inner `afterEach` cleans up the narrow context first, then outer `afterEach` handles the broader cleanup. 159 + Notice the pattern: `beforeAll` and `afterAll` run once for the entire suite, while `beforeEach` and `afterEach` repeat for every test. Within each test, outer `beforeEach` runs first (setting up the broadest context), then inner `beforeEach` runs (narrowing the context). After the test, the order reverses: inner `afterEach` cleans up the narrow context first, then outer `afterEach` handles the broader cleanup. 151 160 152 161 ## Cleanup with `onTestFinished` 153 162
+35
docs/guide/learn/snapshots.md
··· 89 89 90 90 Inline snapshots are great for small, focused values. For large outputs (like a full HTML page), external snapshots or file snapshots are a better fit. 91 91 92 + ::: tip 93 + Unlike external snapshots, inline snapshots don't create separate `.snap` files. The expected value is stored directly in your test file as the argument to `toMatchInlineSnapshot()`, so there's nothing extra to commit. 94 + ::: 95 + 92 96 ## Updating Snapshots 93 97 94 98 When you intentionally change the output of your code, existing snapshots will be outdated and the tests will fail. This is by design; it's the whole point of snapshot testing. But once you've verified that the new output is correct, you need to update the snapshots. ··· 135 139 On the other hand, snapshots are not always the best tool. If the output changes frequently (for instance, it includes timestamps or random IDs), you'll spend more time updating snapshots than they save you. And if you only care about one or two specific fields, a targeted assertion like [`toMatchObject`](/api/expect#tomatchobject) or [`toHaveProperty`](/api/expect#tohaveproperty) expresses your intent more clearly than a snapshot that captures everything. 136 140 137 141 The general rule: use snapshots when you want to protect against *any* change in the output, and use targeted assertions when you only care about *specific* properties. 142 + 143 + ## Handling Dynamic Values 144 + 145 + If your output includes values that change every run (like timestamps or IDs), you can use property matchers to pin the structure while ignoring volatile fields. Pass an object with asymmetric matchers as the first argument to `toMatchSnapshot()` or `toMatchInlineSnapshot()`: 146 + 147 + ```js 148 + test('user snapshot with dynamic fields', () => { 149 + const user = createUser('Alice') 150 + 151 + expect(user).toMatchSnapshot({ 152 + id: expect.any(Number), 153 + createdAt: expect.any(Date), 154 + }) 155 + }) 156 + ``` 157 + 158 + The `id` and `createdAt` fields are checked against the matchers (any number, any date) instead of being compared to a stored value. All other fields are snapshotted as usual. 159 + 160 + ## Error Snapshots 161 + 162 + A common use of inline snapshots is capturing error messages. [`toThrowErrorMatchingInlineSnapshot`](/api/expect#tothrowerrormatchinginlinesnapshot) combines `toThrow` with `toMatchInlineSnapshot` so you can snapshot the error message without a separate `.snap` file: 163 + 164 + ```js 165 + test('throws on invalid input', () => { 166 + expect(() => parse('')).toThrowErrorMatchingInlineSnapshot( 167 + `[Error: Unexpected end of input at position 0]` 168 + ) 169 + }) 170 + ``` 171 + 172 + This is especially handy for verifying that error messages are clear and don't accidentally change. Like other inline snapshots, Vitest fills in the string on the first run and updates it when you press `u`. 138 173 139 174 ::: tip 140 175 For custom snapshot serializers, snapshot matchers, and advanced configuration, see the [Snapshot](/guide/snapshot) guide.
+5 -1
docs/guide/learn/testing-in-practice.md
··· 38 38 }) 39 39 40 40 test('formats EUR prices', () => { 41 - expect(formatPrice(10, 'EUR')).toMatchInlineSnapshot(`"€10.00"`) 41 + expect(formatPrice(10, 'EUR')).toBe('€10.00') 42 42 }) 43 43 44 44 test('handles zero', () => { ··· 429 429 430 430 ::: tip 431 431 Notice that we create a fresh `createTodoList()` in every test. This keeps tests independent, which means they can run in any order without affecting each other. If you find yourself repeating the same setup in every test, that's a good candidate for [`beforeEach`](/api/hooks#beforeeach) or a [`test.extend`](/guide/test-context#extend-test-context) fixture. 432 + ::: 433 + 434 + ::: details What about `nextId`? 435 + The `nextId` counter at the top of the module is shared across all calls to `createTodoList()`, including across tests. This means IDs aren't predictable: one test might get IDs 1 and 2, while another gets 3 and 4 depending on execution order. This works fine here because the tests only check *relative* uniqueness (`first.id !== second.id`), not specific ID values. If a test asserted `expect(todo.id).toBe(1)`, it would break depending on which tests ran before it. When you have shared module-level state like this, make sure your tests don't depend on its specific value. 432 436 ::: 433 437 434 438 ---
+79 -13
docs/guide/learn/writing-tests.md
··· 86 86 87 87 If the default patterns don't work for your project, you can customize which files are included with the [`include`](/config/include) and [`exclude`](/config/exclude) config options. 88 88 89 + ## Testing TypeScript 90 + 91 + Because Vitest runs on top of Vite, TypeScript works out of the box. There's no extra compiler to install, no `ts-jest` to configure, and no separate build step for your tests. Just name your test file `.test.ts` instead of `.test.js` and start writing: 92 + 93 + ```ts 94 + import { expect, test } from 'vitest' 95 + 96 + interface User { 97 + name: string 98 + age: number 99 + } 100 + 101 + function createUser(name: string, age: number): User { 102 + return { name, age } 103 + } 104 + 105 + test('creates a user with the correct fields', () => { 106 + const user = createUser('Alice', 30) 107 + 108 + expect(user).toEqual({ name: 'Alice', age: 30 }) 109 + expect(user.name).toBe('Alice') 110 + }) 111 + ``` 112 + 113 + You can import your production types, use generics, and write typed test utilities exactly as you would in the rest of your codebase. Vite transforms TypeScript on the fly, so tests start fast even in large projects. 114 + 115 + ::: tip 116 + Vitest transforms TypeScript for execution but does **not** type-check your tests during the test run. This is the same trade-off Vite makes for speed: you get fast feedback in the terminal, and run `tsc` or `vitest typecheck` separately when you want full type checking. See the [Testing Types](/guide/testing-types) guide for more details. 117 + ::: 118 + 89 119 ## Reading Test Output 90 120 91 121 When you run `vitest` and only a single test file matches, the output is expanded into a tree structure showing `describe` groups and individual tests along with their duration: ··· 130 160 131 161 These modifiers are great for quick, local changes while developing. For more permanent ways to filter tests (by filename, line number, or tags), see the [Test Filtering](/guide/filtering) guide. 132 162 163 + ## Parameterized Tests 164 + 165 + When you have several test cases that only differ in their inputs and expected outputs, writing a separate `test` for each one gets repetitive. [`test.for`](/api/test#test-for) lets you define the cases as data and run the same test logic for all of them: 166 + 167 + ```js 168 + import { expect, test } from 'vitest' 169 + 170 + test.for([ 171 + [1, 1, 2], 172 + [1, 2, 3], 173 + [2, 1, 3], 174 + ])('add(%i, %i) -> %i', ([a, b, expected]) => { 175 + expect(a + b).toBe(expected) 176 + }) 177 + ``` 178 + 179 + The placeholders `%i`, `%s`, and `%f` in the test name are replaced with the corresponding values from each row, so the output shows `add(1, 1) -> 2`, `add(1, 2) -> 3`, and so on. 180 + 181 + If your cases have more than two or three values, passing objects is more readable. Use `$property` in the name to interpolate fields: 182 + 183 + ```js 184 + test.for([ 185 + { a: 1, b: 1, expected: 2 }, 186 + { a: 1, b: 2, expected: 3 }, 187 + { a: 2, b: 1, expected: 3 }, 188 + ])('add($a, $b) -> $expected', ({ a, b, expected }) => { 189 + expect(a + b).toBe(expected) 190 + }) 191 + ``` 192 + 193 + The second argument to the test function is the [Test Context](/guide/test-context), which gives you access to fixtures, per-test `expect`, and other utilities. This is especially useful with [`test.concurrent`](/api/test#concurrent), where concurrent tests run in parallel and the global `expect` can't reliably associate a snapshot with the right test. The context-scoped `expect` solves this: 194 + 195 + ```js 196 + test.concurrent.for([ 197 + [1, 1], 198 + [1, 2], 199 + [2, 1], 200 + ])('add(%i, %i)', ([a, b], { expect }) => { 201 + expect(a + b).toMatchSnapshot() 202 + }) 203 + ``` 204 + 205 + [`describe.for`](/api/describe#describe-for) works the same way but creates a suite for each set of parameters, which is useful when multiple tests share the same parameterized setup. 206 + 207 + ::: tip 208 + Vitest also provides [`test.each`](/api/test#each), which you may recognize from Jest. It works similarly but spreads array arguments instead of passing them as a single value, and doesn't provide access to the Test Context. It exists mainly for Jest compatibility. Prefer `test.for` in new code. 209 + ::: 210 + 133 211 ## Using Global Imports 134 212 135 213 By default, you import `test`, `expect`, `describe`, and other functions from `vitest` at the top of every test file. If you'd rather use them as globals without importing (similar to how Jest works), you can enable the [`globals`](/config/globals) option in your config: ··· 160 238 161 239 Vitest runs all test files **in parallel** by default, using [child processes](/config/pool). Each test file runs in its own isolated context, so your test files don't share state with each other. This prevents tests in different files from accidentally interfering. 162 240 163 - Tests **within** a single file run sequentially by default, which is usually what you want since tests in the same file often share setup code. If your tests are truly independent, you can opt into running them concurrently with [`test.concurrent`](/api/test#concurrent) to speed things up: 164 - 165 - ```js 166 - test.concurrent('first concurrent test', async () => { 167 - // runs in parallel with the next test 168 - }) 169 - 170 - test.concurrent('second concurrent test', async () => { 171 - // runs in parallel with the previous test 172 - }) 173 - ``` 174 - 175 - See the [Parallelism](/guide/parallelism) guide for more details on controlling test execution. 241 + Tests **within** a single file run sequentially by default, which is usually what you want since tests in the same file often share setup code. If your tests are truly independent, you can opt into running them concurrently with [`test.concurrent`](/api/test#concurrent) to speed things up. See the [Parallelism](/guide/parallelism) guide for more details on controlling test execution.