> ## Documentation Index
> Fetch the complete documentation index at: https://bun.ll1025.cn/llms.txt
> Use this file to discover all available pages before exploring further.

# 快照

> 了解如何在 Bun 中使用快照测试来保存和比较测试运行之间的输出

快照测试保存值的输出，并将其与未来的测试运行进行比较。用于 UI 组件、复杂对象或任何需要保持一致的输出。

## 基本快照

快照测试使用 `.toMatchSnapshot()` 匹配器编写：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("快照", () => {
  expect("foo").toMatchSnapshot();
});
```

此测试首次运行时，Bun 会序列化 `expect` 的参数，并将其写入测试文件旁边的 `__snapshots__` 目录中的快照文件。

### 快照文件

首次运行后，Bun 会创建：

```text title="目录结构" icon="file-directory" theme={null}
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap
```

快照文件包含：

```ts title="__snapshots__/snap.test.ts.snap" icon="file-code" theme={null}
// Bun Snapshot v1, https://bun.sh/docs/test/snapshots

exports[`快照 1`] = `"foo"`;
```

在后续运行中，Bun 会将参数与磁盘上的快照进行比较。

## 更新快照

使用以下命令重新生成快照：

```bash terminal icon="terminal" theme={null}
bun test --update-snapshots
```

当你故意更改了输出或添加了新的快照测试时执行此操作。

## 内联快照

对于较小的值，使用 `.toMatchInlineSnapshot()`。内联快照直接存储在你的测试文件中：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("内联快照", () => {
  // 首次运行：快照将自动插入
  expect({ hello: "world" }).toMatchInlineSnapshot();
});
```

首次运行后，Bun 会自动更新你的测试文件：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("内联快照", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});
```

### 使用内联快照

1. 使用 `.toMatchInlineSnapshot()` 编写测试
2. 运行一次测试
3. Bun 会自动用快照更新你的测试文件
4. 在后续运行中，Bun 会将值与内联快照进行比较

## 错误快照

你也可以使用 `.toThrowErrorMatchingSnapshot()` 和 `.toThrowErrorMatchingInlineSnapshot()` 对错误消息进行快照：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("错误快照", () => {
  expect(() => {
    throw new Error("出了点问题");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("另一个错误");
  }).toThrowErrorMatchingInlineSnapshot();
});
```

运行后，内联版本变为：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
test("错误快照", () => {
  expect(() => {
    throw new Error("出了点问题");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("另一个错误");
  }).toThrowErrorMatchingInlineSnapshot(`"Another error"`);
});
```

## 高级快照用法

### 复杂对象

快照非常适合复杂的嵌套对象：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("复杂对象快照", () => {
  const user = {
    id: 1,
    name: "John Doe",
    email: "john@example.com",
    profile: {
      age: 30,
      preferences: {
        theme: "dark",
        notifications: true,
      },
    },
    tags: ["developer", "javascript", "bun"],
  };

  expect(user).toMatchSnapshot();
});
```

### 数组快照

数组也非常适合快照测试：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("数组快照", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});
```

### 函数输出快照

对函数的输出进行快照：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

function generateReport(data: any[]) {
  return {
    total: data.length,
    summary: data.map(item => ({ id: item.id, name: item.name })),
    timestamp: "2024-01-01", // 为测试固定
  };
}

test("报告生成", () => {
  const data = [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 },
  ];

  expect(generateReport(data)).toMatchSnapshot();
});
```

## React 组件快照

快照非常适合 React 组件：

```tsx title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";

function Button({ children, variant = "primary" }) {
  return <button className={`btn btn-${variant}`}>{children}</button>;
}

test("Button 组件快照", () => {
  const { container: primary } = render(<Button>Click me</Button>);
  const { container: secondary } = render(<Button variant="secondary">Cancel</Button>);

  expect(primary.innerHTML).toMatchSnapshot();
  expect(secondary.innerHTML).toMatchSnapshot();
});
```

## 属性匹配器

对于在测试运行之间变化的数值（如时间戳或 ID），请使用属性匹配器：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { test, expect } from "bun:test";

test("带动态值的快照", () => {
  const user = {
    id: Math.random(), // 每次运行都会变化
    name: "John",
    createdAt: new Date().toISOString(), // 这也变化
  };

  expect(user).toMatchSnapshot({
    id: expect.any(Number),
    createdAt: expect.any(String),
  });
});
```

快照文件存储为：

```txt title="快照文件" icon="file-code" theme={null}
exports[`带动态值的快照 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;
```

## 最佳实践

### 保持快照小巧

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 好：专注的快照
test("用户名称格式化", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// 避免：难以审查的大快照
test("整个页面渲染", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // 可能有数千行
});
```

### 使用描述性测试名称

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 好：清楚快照代表的含义
test("使用美元符号格式化货币", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// 避免：不清楚测试什么
test("格式测试", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});
```

### 分组相关快照

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
import { describe, test, expect } from "bun:test";

describe("Button 组件", () => {
  test("primary 变体", () => {
    expect(render(<Button variant="primary">Click</Button>))
      .toMatchSnapshot();
  });

  test("secondary 变体", () => {
    expect(render(<Button variant="secondary">Cancel</Button>))
      .toMatchSnapshot();
  });

  test("禁用状态", () => {
    expect(render(<Button disabled>Disabled</Button>))
      .toMatchSnapshot();
  });
});
```

### 处理动态数据

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// 好：规范化动态数据
test("API 响应格式", () => {
  const response = {
    data: { id: 1, name: "Test" },
    timestamp: Date.now(),
    requestId: generateId(),
  };

  expect({
    ...response,
    timestamp: "TIMESTAMP",
    requestId: "REQUEST_ID",
  }).toMatchSnapshot();
});

// 或使用属性匹配器
test("带匹配器的 API 响应", () => {
  const response = getApiResponse();

  expect(response).toMatchSnapshot({
    timestamp: expect.any(Number),
    requestId: expect.any(String),
  });
});
```

## 管理快照

### 审查快照变更

当快照发生变化时，请仔细审查：

```bash terminal icon="terminal" theme={null}
# 查看变化
git diff __snapshots__/

# 如果更改是有意的则更新
bun test --update-snapshots

# 提交更新的快照
git add __snapshots__/
git commit -m "UI 更改后更新快照"
```

### 组织大型快照文件

对于大型项目，考虑组织测试以保持快照文件可管理：

```text title="directory structure" icon="file-directory" theme={null}
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap
```

## 故障排除

### 快照失败

当快照失败时，你会看到差异：

```diff title="diff" icon="file-code" theme={null}
- Expected
+ Received

  Object {
-   "name": "John",
+   "name": "Jane",
  }
```

常见原因：

* 有意的更改（使用 `--update-snapshots` 更新）
* 无意的更改（修复代码）
* 动态数据（使用属性匹配器）
* 环境差异（规范化数据）

### 平台差异

注意平台特定的差异：

```ts title="test.ts" icon="https://mintcdn.com/span-inc/82N53aP7NFbaCVSl/icons/typescript.svg?fit=max&auto=format&n=82N53aP7NFbaCVSl&q=85&s=787d39d6a7d96d7f9540dc74344eba23" theme={null}
// Windows/Unix 之间的路径可能不同
test("文件操作", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // 规范化路径
  }).toMatchSnapshot();
});
```
