> For AI agents: the complete documentation index is available at /zh/llms.txt, the full documentation bundle is available at /zh/llms-full.txt.

# Expect

`expect` 用于在测试中创建断言。Rstest 提供了丰富的 API 及匹配器，支持轮询、快照断言等。

你可以从 `@rstest/core` 包中导入 `expect` API：

```ts
import { expect, test } from '@rstest/core';

test('should add two numbers correctly', () => {
  expect(1 + 1).toBe(2);
  expect(1 + 2).toBe(3);
});
```

你也可以从 [测试上下文](/zh/api/runtime-api/test-api/test.md#testcontext) 中获取 `expect` API，这有助于在并行测试中准确追踪断言的来源。

```ts
import { test } from '@rstest/core';

test('should add two numbers correctly', ({ expect }) => {
  expect(1 + 1).toBe(2);
  expect(1 + 2).toBe(3);
});
```

## expect

- **类型：** `<T>(actual: T, message?: string) => Assertion<T>`

为给定的值创建一个断言对象。

- `actual`：要断言的目标值。
- `message`：可选，自定义失败提示信息，会在断言失败时显示。

```ts
import { expect } from '@rstest/core';

expect(1 + 1).toBe(2);
expect('hello').toBeDefined();
expect([1, 2, 3]).toContain(2);
```

## expect.element（Browser Mode）

- **类型：** `(locator: Locator) => BrowserElementExpect`

在 Browser Mode 中，`expect.element` 用于对 `@rstest/browser` 提供的 Locator 执行断言（例如 `toBeVisible`、`toHaveText`、`toHaveValue`）。

```ts
import { page } from '@rstest/browser';
import { expect, test } from '@rstest/core';

test('asserts by locator', async () => {
  document.body.innerHTML = '<button>Save</button>';
  await expect
    .element(page.getByRole('button', { name: 'Save' }))
    .toBeVisible();
});
```

`expect.element` 仅在 Browser Mode 中可用，并且需要引入 `@rstest/browser` 来安装浏览器侧适配层。完整参考见 [Assertion（Browser Mode）](/zh/api/runtime-api/browser-mode/assertion.md)。

## expect.not

否定该断言。

```ts
expect(1 + 1).not.toBe(3);
expect('foo').not.toBeUndefined();
```

## expect.soft

- **类型：** `<T>(actual: T, message?: string) => Assertion<T>`

即使断言失败，测试也会继续执行，所有失败会在最后统一报告。

```ts
expect.soft(1 + 1).toBe(3); // 不会中断测试
expect.soft(1 + 2).toBe(4);
```

## expect.poll

- **类型：** `<T>(actual: () => T | Promise<T>, options?: { interval?: number, timeout?: number, message?: string }) => PromiseLike<Assertion<Awaited<T>>>`

持续调用 `actual`，并重复执行 matcher，直到断言通过或超时。

- **默认 `interval`：** `50`（毫秒）
- **默认 `timeout`：** `1000`（毫秒），Browser Mode 下为 `5000`（毫秒）

你可以通过 [`expect.poll` 配置](/zh/config/test/expect.md#expectpoll)修改这些默认值。单次调用传入的选项优先级更高。

`options`：

- `interval`：每次重试之间的间隔时间（毫秒）。
- `timeout`：轮询允许的最长总耗时（毫秒）。
- `message`：轮询断言失败时显示的自定义提示信息。

注意：

- `expect.poll(...)` 是异步断言，**必须**使用 `await`。
- `expect.poll(...)` 不支持 `resolves`/`rejects`、`toThrow*` 以及快照类 matcher。遇到这类不稳定条件建议使用 `rs.waitFor()`。

```ts
await expect.poll(() => getStatus()).toBe('ready');

await expect
  .poll(
    async () => {
      const response = await fetch('/api/health');
      const data = await response.json();
      return data.status;
    },
    {
      interval: 100,
      timeout: 5000,
      message: '服务应在 5 秒内恢复健康',
    },
  )
  .toBe('healthy');
```

## expect.unreachable

- **类型：** `(message?: string) => never`

标记代码路径为不可达。如果调用会抛出异常。

```ts
if (shouldNotHappen) {
  expect.unreachable('这里不应该被执行');
}
```

## expect.assertions

- **类型：** `(expected: number) => void`

验证在测试期间调用了特定数量的断言。常用来检查异步代码是否被调用或是否触发了预期的异常。

```ts
expect.assertions(1);

try {
  await someAsyncFunction();
} catch (e) {
  expect(e).toBeInstanceOf(Error);
}
```

## expect.hasAssertions

- **类型：** `() => void`

验证在测试期间至少调用了一个断言。

适用于断言次数不固定，但必须至少发生一次断言的场景。

```ts
expect.hasAssertions();
expect(1 + 1).toBe(2);
```

## expect.addEqualityTesters

- **类型：** `(testers: Array<Tester>) => void`

自定义用来验证两个对象是否相等的测试器。

每个 tester 可以返回：

- `true`：两者视为相等
- `false`：两者视为不相等
- `undefined`：跳过当前 tester，交给下一个 tester 或默认比较逻辑

```ts
expect.addEqualityTesters([
  (a, b) => {
    if (typeof a === 'number' && typeof b === 'number') {
      return Math.abs(a - b) < 0.01;
    }
  },
]);
expect(0.1 + 0.2).toEqual(0.3); // 使用自定义测试器后为 true
```

## expect.addSnapshotSerializer

- **类型：** `(serializer: SnapshotSerializer) => void`

为快照测试添加自定义序列化工具。

```ts
expect.addSnapshotSerializer({
  test: (val) => typeof val === 'string' && val.startsWith('secret:'),
  print: (val) => '***MASKED***',
});
expect('secret:123').toMatchSnapshot(); // 快照输出的 secret 信息会被掩码
```

## expect.getState / expect.setState

- **类型：**
  - `getState: () => MatcherState`
  - `setState: (state: Partial<MatcherState>) => void`

获取或设置内部匹配器状态。

```ts
const state = expect.getState();
console.log(state.currentTestName);
expect.setState({ currentTestName: '自定义名称' });
console.log(expect.getState().currentTestName); // 输出 '自定义名称'
```

## 匹配器（Matchers）

### 常用匹配器

常用匹配器是最常见的一组断言能力，用来覆盖值比较、结构比较、类型判断和异常验证。

相等性校验：

- `toBe(value)`：基于 `Object.is` 的严格相等比较。
- `toEqual(value)`：对象/数组的深度相等比较。
- `toStrictEqual(value)`：更严格的深度相等（包含 `undefined` 字段、稀疏数组差异）。
- `toBeOneOf(array)`：值必须命中给定候选集合之一。

类型与存在性：

- `toBeTruthy()` / `toBeFalsy()`：真值/假值判断。
- `toBeNull()` / `toBeUndefined()` / `toBeDefined()`：空值与定义状态判断。
- `toBeNaN()`：判断是否为 `NaN`。
- `toBeInstanceOf(class)`：实例类型判断。
- `toBeTypeOf(type)`：运行时类型判断（如 `'string'`）。

数字比较：

- `toBeGreaterThan(number)` / `toBeGreaterThanOrEqual(number)`。
- `toBeLessThan(number)` / `toBeLessThanOrEqual(number)`。
- `toBeCloseTo(number, numDigits?)`：适合浮点数近似比较。

集合与字符串：

- `toContain(item)`：数组/字符串包含判断。
- `toContainEqual(item)`：数组成员的深度相等判断。
- `toMatch(stringOrRegExp)`：字符串匹配子串或正则。
- `toMatchObject(object)`：对象包含指定属性子集。
- `toHaveLength(length)`：断言 `.length`。
- `toHaveProperty(path, value?)`：断言属性路径存在，可选断言该路径值。

自定义条件与异常：

- `toSatisfy(fn)`：满足自定义谓词函数。
- `toThrowError(expected?)`：断言函数抛错（支持字符串/正则/错误类型）。

```ts
// toBe vs toEqual
expect(1 + 1).toBe(2);
expect({ id: 1, name: 'a' }).toEqual({ id: 1, name: 'a' });

// 严格深比较
expect({ a: undefined, b: 1 }).toStrictEqual({ a: undefined, b: 1 });

// 数字断言
expect(0.1 + 0.2).toBeCloseTo(0.3, 5);
expect(10).toBeGreaterThan(5);

// 集合断言
expect([1, 2, 3]).toContain(2);
expect([{ id: 1 }]).toContainEqual({ id: 1 });
expect({ user: { role: 'admin' } }).toHaveProperty('user.role', 'admin');
expect({ id: 1, role: 'admin', active: true }).toMatchObject({
  id: 1,
  role: 'admin',
});

// 文本与自定义条件
expect('request id: 42').toMatch(/id:\s\d+/);
expect(21).toSatisfy((n) => n % 3 === 0);

// 异常断言（必须包成函数）
expect(() => {
  throw new TypeError('invalid input');
}).toThrowError(TypeError);
```

### Mock 匹配器

Mock 匹配器用于验证 `rs.fn` / `rs.spyOn` 的交互行为，包括调用次数、参数、调用顺序、返回值和异步结果。

调用相关：

- `toHaveBeenCalled()`：至少被调用一次。
- `toHaveBeenCalledTimes(times)`：被调用次数精确匹配。
- `toHaveBeenCalledWith(...args)`：任意一次调用参数匹配。
- `toHaveBeenLastCalledWith(...args)`：最后一次调用参数匹配。
- `toHaveBeenNthCalledWith(n, ...args)`：第 n 次调用参数匹配。
- `toHaveBeenCalledExactlyOnceWith(...args)`：仅调用一次且参数匹配。
- `toHaveBeenCalledBefore(mock)` / `toHaveBeenCalledAfter(mock)`：校验调用先后顺序。

返回值相关：

- `toHaveReturned()`：至少有一次正常返回。
- `toHaveReturnedTimes(times)`：正常返回次数精确匹配。
- `toHaveReturnedWith(value)`：任意一次返回值匹配。
- `toHaveLastReturnedWith(value)`：最后一次返回值匹配。
- `toHaveNthReturnedWith(n, value)`：第 n 次返回值匹配。

Promise 结果相关：

- `toHaveResolved()`：至少有一次 resolve。
- `toHaveResolvedTimes(times)`：resolve 次数精确匹配。
- `toHaveResolvedWith(value)`：任意一次 resolve 值匹配。
- `toHaveLastResolvedWith(value)`：最后一次 resolve 值匹配。
- `toHaveNthResolvedWith(n, value)`：第 n 次 resolve 值匹配。

注意：

- `Nth` 系列从 `1` 开始计数（`1` 表示第一次调用）。
- `toHaveBeenCalledWith` 只要求“某次”匹配；需要严格位置时用 `Last` 或 `Nth`。
- 这些 matcher 只能用于 mock/spy，不能用于普通函数。

```ts
const sum = rs.fn((a: number, b: number) => a + b);
sum(1, 2);
sum(2, 3);

expect(sum).toHaveBeenCalledTimes(2);
expect(sum).toHaveBeenNthCalledWith(1, 1, 2);
expect(sum).toHaveBeenLastCalledWith(2, 3);
expect(sum).toHaveReturnedWith(3);
expect(sum).toHaveLastReturnedWith(5);

const fetchUser = rs.fn(async (id: number) => ({ id, name: 'alice' }));
await fetchUser(1);
await fetchUser(2);

expect(fetchUser).toHaveResolvedTimes(2);
expect(fetchUser).toHaveNthResolvedWith(1, { id: 1, name: 'alice' });

const before = rs.fn();
const after = rs.fn();
before();
after();
expect(before).toHaveBeenCalledBefore(after);
```

### 快照匹配器

快照匹配器用于将值、错误或文件与之前记录的快照进行比较，便于追踪输出的变化。

- `toMatchSnapshot()`：将值与已保存的快照进行比较。
- `toMatchInlineSnapshot()`：将值与测试文件中的内联快照进行比较。
- `toThrowErrorMatchingSnapshot()`：检查抛出的错误与已保存快照匹配。
- `toThrowErrorMatchingInlineSnapshot()`：检查抛出的错误与内联快照匹配。
- `toMatchFileSnapshot(filepath)`：将值与指定文件中的快照进行比较。

```ts
expect('hello world').toMatchSnapshot();

expect(() => {
  throw new Error('fail');
}).toThrowErrorMatchingSnapshot();

await expect('hello world').toMatchFileSnapshot(
  '__snapshots__/file.output.txt',
);
```

### Promise 匹配器

- `resolves`：对 Promise 的 resolve 结果进行断言。
- `rejects`：对 Promise 的 reject 结果进行断言。

```ts
await expect(Promise.resolve('ok')).resolves.toBe('ok');
await expect(Promise.reject(new Error('fail'))).rejects.toThrow('fail');
```

### 不对称匹配器

不对称匹配器是一些辅助工具，允许更灵活地匹配值，如部分匹配、类型匹配或模式匹配，适合编写更具表现力且不易碎的测试。

- `expect.anything()`：匹配除 null 和 undefined 之外的任意值。
- `expect.any(constructor)`：匹配指定类型的任意值。
- `expect.closeTo(number, precision?)`：匹配接近期望值的数字。
- `expect.arrayContaining(array)`：匹配包含期望元素的数组。
- `expect.objectContaining(object)`：匹配包含期望属性的对象。
- `expect.stringContaining(string)`：匹配包含期望子串的字符串。
- `expect.stringMatching(stringOrRegExp)`：匹配符合期望模式的字符串。

```ts
expect({ a: 1 }).toEqual({ a: expect.anything() });

expect(1).toEqual(expect.any(Number));

expect(0.1 + 0.2).toEqual(expect.closeTo(0.3, 5));

expect([1, 2, 3]).toEqual(expect.arrayContaining([2, 1]));

expect({ a: 1, b: 2 }).toEqual(expect.objectContaining({ a: 1 }));

expect('hello world').toEqual(expect.stringContaining('world'));

expect('hello world').toEqual(expect.stringMatching(/^hello/));
```

### 自定义匹配器

你可以通过扩展 expect 添加自定义匹配器：

```ts
expect.extend({
  toBeDivisibleBy(received, argument) {
    const pass = received % argument === 0;
    if (pass) {
      return {
        message: () => `期望 ${received} 不能被 ${argument} 整除`,
        pass: true,
      };
    } else {
      return {
        message: () => `期望 ${received} 能被 ${argument} 整除`,
        pass: false,
      };
    }
  },
});

expect(10).toBeDivisibleBy(2);
```

如果你想为自定义匹配器添加 TypeScript 类型定义，可以扩展 `@rstest/core` 模块中的 `Assertion` 接口声明：

```ts
declare module '@rstest/core' {
  interface Assertion<T> {
    toBeDivisibleBy(argument: number): void;
  }
}
```
