Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions .vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -906,7 +906,7 @@ export default ({ mode }: { mode: string }) => {
link: '/guide/test-annotations',
},
{
text: '扩展断言',
text: '扩展匹配器',
link: '/guide/extending-matchers',
},
{
Expand Down Expand Up @@ -1020,27 +1020,27 @@ export default ({ mode }: { mode: string }) => {
collapsed: false,
items: [
{
text: 'Database Transaction per Test',
text: '一个测试对应一个数据库事务',
link: '/guide/recipes/db-transaction',
},
{
text: 'Cancelling Long-Running Operations Gracefully',
text: '优雅地取消长时间运行的操作',
link: '/guide/recipes/cancellable',
},
{
text: 'Waiting for Async Conditions',
text: '等待异步条件',
link: '/guide/recipes/wait-for',
},
{
text: 'Type Narrowing in Tests',
text: '在测试中收窄类型',
link: '/guide/recipes/type-narrowing',
},
{
text: 'Custom Assertion Helpers',
text: '自定义断言工具函数',
link: '/guide/recipes/custom-assertions',
},
{
text: 'Watching Non-Imported Files',
text: '监视非直接导入的文件',
link: '/guide/recipes/watch-templates',
},
{
Expand Down
2 changes: 1 addition & 1 deletion api/expect.md
Original file line number Diff line number Diff line change
Expand Up @@ -2277,7 +2277,7 @@ declare module 'vitest' {
:::

:::tip
如果想了解更多信息,请查看 [扩展断言](/guide/extending-matchers)。
如果想了解更多信息,请查看 [扩展匹配器](/guide/extending-matchers)。
:::

## expect.addEqualityTesters {#expect-addequalitytesters}
Expand Down
6 changes: 3 additions & 3 deletions guide/extending-matchers.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
---
title: 扩展断言 | 指南
title: 扩展匹配器 | 指南
---

# 扩展断言 {#extending-matchers}
# 扩展匹配器 {#extending-matchers}

由于 Vitest 兼容 Chai 和 Jest,所以可以根据个人喜好使用 [`chai.use`](https://www.chaijs.com/guide/plugins/) API 或者 `expect.extend`。

本文将以 `expect.extend` 为例探讨扩展断言。如果你对 Chai 的 API 更感兴趣,可以查看 [他们的指南](https://www.chaijs.com/guide/plugins/)。
本文将以 `expect.extend` 为例探讨扩展匹配器。如果你对 Chai 的 API 更感兴趣,可以查看 [他们的指南](https://www.chaijs.com/guide/plugins/)。

为了扩展默认的断言,可以使用对象包裹断言的形式调用 `expect.extend` 方法。

Expand Down
28 changes: 14 additions & 14 deletions guide/recipes/cancellable.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: Cancellable Test Resources | Recipes
title: 可取消的测试资源 | 技巧
---

# Cancellable Test Resources
# 可取消的测试资源 {#cancellable-test-resources}

A test can hold onto resources that don't stop when the test stops. A `fetch`, a child process, a file stream, a polling loop: none of those notice when Vitest has cancelled the test, and the worker has to sit there waiting for them to finish on their own. Vitest cancels a test when it exceeds its `timeout`, when another test fails under `--bail`, or when someone presses <kbd>Ctrl</kbd>+<kbd>C</kbd> in the terminal.
测试运行过程中可能占用一些资源,测试停止后这些资源不会随之释放。无论是 `fetch`、子进程、文件流还是轮询,它们都无法感知 Vitest 已取消测试,导致工作进程只能被动等待它们自行结束 Vitest 会在测试超时超过 `timeout` 限制、在 `--bail` 模式下另一个测试失败,或有人在终端按下 <kbd>Ctrl</kbd>+<kbd>C</kbd> 时取消测试。

The test context provides a [`signal`](/guide/test-context#signal) <Version>3.2.0</Version> that fires in all of those cases. Pass it to anything that accepts an `AbortSignal` and the resource is released when Vitest cancels.
测试上下文提供了 [`signal`](/guide/test-context#signal) <Version>3.2.0</Version>,它会在上述所有情况下触发。将它传递给任何接受 `AbortSignal` 的对象,当 Vitest 取消测试时对应的资源就会被释放。

## Pattern
## 示例 {#pattern}

```ts
import { test } from 'vitest'
Expand All @@ -18,19 +18,19 @@ test('stop request when test times out', async ({ signal }) => {
}, 2000)
```

If the request hasn't completed within 2 seconds, `fetch` rejects with `AbortError` instead of the test hanging until the operation finishes.
如果请求未在 2 秒内完成,`fetch` 会抛出 `AbortError`,而不会让测试挂起直到操作结束。

## Other Web APIs that accept an `AbortSignal`
## 其他接受 `AbortSignal` 的 Web API {#other-web-apis-that-accept-an-abortsignal}

- [`fetch`](https://developer.mozilla.org/docs/Web/API/fetch)
- [`addEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener), where passing `{ signal }` removes the listener on abort
- [`addEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener),传递 `{ signal }` 会在中止时移除监听器
- [`ReadableStream.pipeTo`](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo)
- Node.js APIs like [`fs.readFile`](https://nodejs.org/api/fs.html#fspromisesreadfilepath-options), [`child_process.spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options), and [`setTimeout` or `setInterval`](https://nodejs.org/api/timers.html), all of which accept `{ signal }`
- Any custom code that calls `signal.throwIfAborted()` or listens for `'abort'`
- Node.js API [`fs.readFile`](https://nodejs.org/api/fs.html#fspromisesreadfilepath-options), [`child_process.spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options),和 [`setTimeout` `setInterval`](https://nodejs.org/api/timers.html),它们都接受 `{ signal }`
- 任何调用 `signal.throwIfAborted()` 或监听 `'abort'` 的代码

## Forwarding the signal
## 传递信号 {#forwarding-the-signal}

Wire the test's signal into your own helpers so cancellation propagates all the way down:
将测试的信号接入你自己的工具函数,使取消操作向下传递:

```ts
async function pollUntilReady(url: string, signal: AbortSignal) {
Expand All @@ -49,8 +49,8 @@ test('worker becomes ready', async ({ signal }) => {
}, 5000)
```

## See also
## 相关链接 {#see-also}

- [`signal` in Test Context](/guide/test-context#signal)
- [测试上下文中的 `signal`](/guide/test-context#signal)
- [`bail`](/config/bail)
- [`testTimeout`](/config/testtimeout)
30 changes: 15 additions & 15 deletions guide/recipes/custom-assertions.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
---
title: Custom Assertion Helpers | Recipes
title: 自定义断言工具函数 | 技巧
---

# Custom Assertion Helpers
# 自定义断言工具函数 {#custom-assertion-helpers}

Reusable assertion helpers make tests easier to read, at the cost of stack traces. When an assertion fails inside a helper, the trace points at the line inside the helper rather than the test that called it. With the same helper used across many tests, the stack trace alone doesn't identify which call site failed.
可复用的断言工具函数会让测试更易读,但代价是堆栈跟踪会变得不直观。当工具函数内的断言失败时,堆栈跟踪会指向工具函数内部的那一行,而不是调用它的测试。当同一个工具函数在多个测试中被使用时,仅凭堆栈跟踪无法识别是哪个具体位置调用失败。

[`vi.defineHelper`](/api/vi#vi-defineHelper) <Version>4.1.0</Version> wraps a function so Vitest strips its internals from the stack and points the error back at the call site instead.
[`vi.defineHelper`](/api/vi#vi-defineHelper) <Version>4.1.0</Version> 会包装一个函数,让 Vitest 在堆栈中移除工具函数的内部实现,并将错误指向调用点。

## Pattern
## 示例 {#pattern}

```ts
import { expect, test, vi } from 'vitest'

const assertPair = vi.defineHelper((a: unknown, b: unknown) => {
expect(a).toEqual(b) // ❌ failure does NOT point here
expect(a).toEqual(b) // ❌ 失败不指向这里
})

test('example', () => {
assertPair('left', 'right') // ✅ failure points here
assertPair('left', 'right') // ✅ 失败指向这里
})
```

When `assertPair` fails, the diff and stack frame surface the test line that called it. That's the same behaviour built-in matchers give you.
`assertPair` 失败时,差异对比和堆栈帧会显示调用它的测试行。和内置匹配器提供的行为一致。

## Composing multiple expectations
## 组合多个期望 {#composing-multiple-expectations}

The same wrapper works for helpers that bundle several assertions:
同一个包装器也适用于将多个断言打包的工具函数:

```ts
import { expect, test, vi } from 'vitest'
Expand All @@ -43,13 +43,13 @@ test('returns a valid user', async () => {
})
```

A failure in any of the inner `expect` calls is reported against the `expectValidUser(user)` line in the test.
内部任意 `expect` 调用失败时,都会在测试中的 `expectValidUser(user)` 这一行上报。

Reach for `defineHelper` whenever a reusable check calls `expect` more than once, whether that's a domain-specific helper like `expectValidJWT` or any block of `expect` calls you'd otherwise inline into every test.
只要可复用的检查包含多次 `expect` 调用,都可以使用 `defineHelper`,无论是像 `expectValidJWT` 这样的领域特定工具函数,还是任何原本要内联到每个测试中的 `expect` 调用块。

For asymmetric matchers and custom matchers attached to `expect.extend`, see [Extending Matchers](/guide/extending-matchers).
关于附加到 `expect.extend` 的不对称匹配器和自定义匹配器,请参阅 [扩展匹配器](/guide/extending-matchers)

## See also
## 相关链接 {#see-also}

- [`vi.defineHelper`](/api/vi#vi-defineHelper)
- [Extending Matchers](/guide/extending-matchers)
- [扩展匹配器](/guide/extending-matchers)
37 changes: 19 additions & 18 deletions guide/recipes/db-transaction.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: Database Transaction per Test | Recipes
title: 一个测试对应一个数据库事务 | 技巧
---

# Database Transaction per Test
# 一个测试对应一个数据库事务 {#database-transaction-per-test}

Integration tests that touch a real database need to start from a clean state. Truncating tables between every test is slow, so the conventional workaround is to wrap each test in a transaction that's rolled back when it finishes. Nothing ever commits, and there's no per-test cleanup to write.
涉及到真实数据库的集成测试需要从一个干净的状态开始。在每个测试前清空表数据很慢,因此常用的做法是将每个测试包装在一个事务中,并在测试完成时进行回滚。这样永远不会提交事务、也不需要每次都编写清理代码。

Vitest exposes this through [`aroundEach`](/api/hooks#aroundeach) <Version>4.1.0</Version> and a [scoped fixture](/guide/test-context#fixture-scopes) <Version>3.2.0</Version>.
Vitest 通过 [`aroundEach`](/api/hooks#aroundeach) <Version>4.1.0</Version> [scoped fixture](/guide/test-context#fixture-scopes) <Version>3.2.0</Version> 提供了这一能力。

## Pattern
## 示例 {#pattern}

```ts
import { test as baseTest } from 'vitest'
Expand All @@ -27,19 +27,20 @@ test.aroundEach(async (runTest, { db }) => {

test('insert user', async ({ db }) => {
await db.insert({ name: 'Alice' })
// rolled back automatically when the test ends
// 在测试结束时自动回滚
})
```

## How it works
## 工作原理 {#how-it-works}

The `db` fixture is created once per file via `scope: 'file'`, so connection setup happens once instead of on every test, and `onCleanup` closes the connection when the file is done. `aroundEach` wraps every test in `db.transaction(runTest)`, and anything the test writes gets rolled back when `runTest` resolves. The test receives the same `db` instance through its context, with no awareness that it's running inside a transaction.
`db` fixture 通过 `scope: 'file'` 在每个文件级别创建一次,因此连接建立只发生一次,而不是在每个测试中重复进行,并且 `onCleanup` 会在文件测试执行完成时关闭连接。`aroundEach` 将每个测试包装在 `db.transaction(runTest)` 中,测试写入的所有内容在 `runTest`
解析时都会回滚。测试通过上下文使用相同的 `db` 实例,无需感知它正运行在事务中。

This works as long as your database driver supports nested transactions or savepoints, which covers most modern databases. The same `aroundEach` hook can also wrap an [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) context if you want to propagate things like tenant or trace IDs through the test alongside the transaction.
只要你的数据库驱动支持嵌套事务或保存点,这种形式就能正常工作,大多数现代数据库都满足这一条件。如果你想要在测试中传播租户或追踪 ID 等内容,也可以使用同一个 `aroundEach` 钩子来包装 [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) 上下文,与事务一起使用。

## One connection per worker
## 一个工作进程一个连接 {#one-connection-per-worker}

If the suite has many files, paying for a fresh database connection on every file adds up. Switching the fixture to `scope: 'worker'` and turning off isolation lets multiple files share a single connection per worker process:
如果测试套件有很多文件,在每个文件上建立新的数据库连接仍会增加开销。将 fixture 切换到 `scope: 'worker'` 并关闭隔离可以让多个文件在每个工作进程内共享一个连接:

```ts [vitest.config.ts]
import { defineConfig } from 'vitest/config'
Expand Down Expand Up @@ -67,14 +68,14 @@ test.aroundEach(async (runTest, { db }) => {
})
```

By default, every test file runs in its own worker, so `scope: 'file'` and `scope: 'worker'` behave identically. With `isolate: false`, Vitest reuses workers across files (capped by [`maxWorkers`](/config/maxworkers)), so a worker-scoped fixture is created once per worker instead of once per file. For a suite of 200 files running on 8 workers, that's 8 connections instead of 200.
默认情况下,每个测试文件在自己的工作进程中运行,因此 `scope: 'file'` `scope: 'worker'` 行为相同。使用 `isolate: false` 时,Vitest 会在文件之间复用工作进程(数量上限由 [`maxWorkers`](/config/maxworkers) 限制),因此工作进程范围的 fixture 在每个工作进程中只创建一次,而不是在每个文件中都创建一次。对于 200 个文件、8 个工作进程的测试套件,只需 8 个连接,而非 200 个。

Reusing workers isn't a free optimization. With isolation off, files share module instances inside the worker, and tests that mutate top-level state (counters, caches, monkey-patched globals) can leak that state to whichever file runs next in the same worker. The per-test rollback handles data isolation in the database. It can't protect module state in the worker. Read the trade-offs in the [Per-File Isolation Settings](/guide/recipes/disable-isolation) recipe before turning isolation off suite-wide.
复用工作进程并不是零成本的优化。在禁用隔离后,文件在工作进程内共享模块实例,在修改模块顶层变量(计数器、缓存、猴子补丁(monkey-patched)的全局变量)的测试,可能会将状态泄漏到同一工作进程内后续运行的文件中。每次测试的回滚负责处理数据库中的数据隔离,但无法保护工作进程中的模块状态。在全局范围内关闭隔离之前,请参阅 [每个文件的隔离设置](/guide/recipes/disable-isolation) 技巧中的权衡。

[`vmThreads` and `vmForks`](/config/pool) always run isolated regardless of the `isolate` flag, so worker-scoped fixtures fall back to per-file behavior in those pools.
[`vmThreads` `vmForks`](/config/pool) 始终以隔离模式运行,无论 `isolate` 参数如何设置,在这些工作进程池中,工作进程范围的 fixture 会退化为每个文件创建一次的行为。

## See also
## 相关链接 {#see-also}

- [`aroundEach` and `aroundAll`](/api/hooks#aroundeach)
- [Fixture scopes](/guide/test-context#fixture-scopes)
- [Builder pattern](/guide/test-context#builder-pattern)
- [`aroundEach` `aroundAll`](/api/hooks#aroundeach)
- [Fixture 作用域](/guide/test-context#fixture-scopes)
- [构建模式](/guide/test-context#builder-pattern)
Loading
Loading