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

# 集成测试

> 通过组织测试、管理密钥、处理不稳定性以及控制成本，使用真实的 LLM API 测试智能体。

集成测试用于验证您的智能体能否与模型 API 和外部服务正确协同工作。与使用模拟和桩的[单元测试](/oss/javascript/langchain/test/unit-testing)不同，集成测试会进行实际的网络调用，以确认组件能协同工作、凭证有效且延迟在可接受范围内。

由于 LLM 的响应具有不确定性，集成测试需要采用与传统软件测试不同的策略。本指南将介绍如何为您的智能体组织、编写和运行集成测试。关于为 LangChain 本身贡献代码时的一般测试基础设施，请参阅[代码贡献指南](/oss/javascript/contributing/code#running-tests)。

## 分离单元测试和集成测试

集成测试速度较慢且需要 API 凭证，因此请将其与单元测试分开。这样您可以在每次更改时运行快速的单元测试，而将集成测试保留给 CI 或部署前检查。

使用文件命名约定来分离集成测试。将集成测试文件命名为 `*.int.test.ts`，并配置 vitest 在默认运行中排除它们：

```ts vitest.config.ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { configDefaults, defineConfig } from "vitest/config";

export default defineConfig((env) => {
  if (env.mode === "int") {
    return {
      test: {
        testTimeout: 100_000,
        include: ["**/*.int.test.ts"],
        setupFiles: ["dotenv/config"],
      },
    };
  }

  return {
    test: {
      testTimeout: 30_000,
      exclude: ["**/*.int.test.ts", ...configDefaults.exclude],
    },
  };
});
```

在 `package.json` 中添加脚本：

```json theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
{
  "scripts": {
    "test": "vitest",
    "test:integration": "vitest --mode int"
  }
}
```

显式运行集成测试：

```bash theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
npm run test:integration
```

## 管理 API 密钥

集成测试需要真实的 API 凭证。请从环境变量中加载它们，以确保密钥不会进入源代码管理。

将 `dotenv/config` 添加为 vitest 的 setup 文件，以便环境变量能自动从 `.env` 加载：

```ts vitest.config.ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export default defineConfig({
  test: {
    setupFiles: ["dotenv/config"],
  },
});
```

```bash .env theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
OPENAI_API_KEY=sk-...
```

当密钥缺失时跳过测试：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { test } from "vitest";

test.skipIf(!process.env.OPENAI_API_KEY)(
  "agent responds with tool call",
  async () => {
    // ...
  }
);
```

<Warning>
  将 `.env` 添加到您的 `.gitignore` 文件中，以避免提交凭证。在 CI 中，通过您提供商（例如 GitHub Actions secrets）的密钥管理功能注入密钥。
</Warning>

## 断言结构，而非内容

LLM 的响应在不同运行之间会有所变化。与其断言确切的输出字符串，不如验证响应的结构属性：消息类型、工具调用名称、参数形状和消息数量。

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
test("agent calls weather tool", async () => {
  const agent = createAgent({ model: "claude-sonnet-4-6", tools: [getWeather] });
  const result = await agent.invoke({
    messages: [new HumanMessage("What's the weather in SF?")]
  });

  const aiMsg = result.messages.find(
    (m) => AIMessage.isInstance(m) && m.tool_calls?.length
  );
  expect(aiMsg).toContainToolCall({ name: "get_weather" });
  expect(result.messages.at(-1)).toBeAIMessage();
});
```

此示例使用了[自定义测试匹配器](#use-custom-test-matchers)。有关设置和完整的匹配器参考，请参阅下文。

<Tip>
  要进行更严格的轨迹断言，请使用 [AgentEvals](/oss/javascript/langchain/test/evals) 评估器，它支持 `unordered` 和 `superset` 等模糊匹配模式。
</Tip>

## 使用自定义测试匹配器

`langchain` 提供了[自定义的 vitest 匹配器](https://vitest.dev/guide/extending-matchers.html)，使结构断言更具可读性，并在失败时产生清晰的错误信息。在 setup 文件中注册一次，它们就可以在每次 `expect()` 调用中使用。

### 设置

添加一个 vitest setup 文件，用 LangChain 匹配器扩展 `expect`：

```ts vitest.setup.ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
import { langchainMatchers } from "@langchain/core/testing";

expect.extend(langchainMatchers);
```

在您的 vitest 配置中引用它：

```ts vitest.config.ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
export default defineConfig({
  test: {
    setupFiles: ["vitest.setup.ts"],
  },
});
```

TypeScript 类型已自动包含，因此无需额外配置即可获得自动补全。

### 检查消息类型

每个消息类都有一个对应的匹配器：`toBeHumanMessage()`、`toBeAIMessage()`、`toBeSystemMessage()` 和 `toBeToolMessage()`。不带参数调用仅检查类型，或传递字符串以同时匹配内容：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const response = await agent.invoke({
  messages: [new HumanMessage("What's the weather?")]
});
const lastMessage = response.messages.at(-1);

expect(lastMessage).toBeAIMessage();
expect(lastMessage).toBeAIMessage("It's 72°F and sunny.");
```

传递一个对象以匹配特定字段：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
expect(lastMessage).toBeAIMessage({ name: "weather-bot" });
expect(toolMsg).toBeToolMessage({ tool_call_id: "call_1" });
```

### 断言工具调用

有三个匹配器用于处理 [`AIMessage`](https://reference.langchain.com/javascript/langchain-core/messages/AIMessage) 上的工具调用断言：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const response = await agent.invoke({
  messages: [new HumanMessage("Weather in SF and NYC?")]
});
const aiMsg = response.messages.find(
  (m) => AIMessage.isInstance(m) && m.tool_calls?.length
);

// 检查是否存在特定的工具调用（顺序无关）
expect(aiMsg).toHaveToolCalls([
  { name: "get_weather", args: { city: "San Francisco" } },
  { name: "get_weather", args: { city: "New York" } },
]);

// 仅检查数量
expect(aiMsg).toHaveToolCallCount(2);

// 检查是否至少有一个工具调用匹配（支持 .not）
expect(aiMsg).toContainToolCall({ name: "get_weather" });
expect(aiMsg).not.toContainToolCall({ name: "send_email" });
```

### 断言工具消息

`toHaveToolMessages()` 接收完整的消息数组，并按顺序检查其中的 [`ToolMessage`](https://reference.langchain.com/javascript/langchain-core/messages/ToolMessage) 实例：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
expect(response.messages).toHaveToolMessages([
  { content: "72°F and sunny in San Francisco" },
  { content: "68°F and cloudy in New York" },
]);
```

### 断言中断和结构化响应

`toHaveBeenInterrupted()` 检查 [LangGraph 中断](/oss/javascript/langchain/human-in-the-loop) 结果中是否存在 `__interrupt__` 字段。传递一个值以匹配中断负载：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const result = await graph.invoke(input);

expect(result).toHaveBeenInterrupted();
expect(result).toHaveBeenInterrupted("confirm_action");
```

`toHaveStructuredResponse()` 检查结果上是否存在 `structuredResponse` 字段。传递一个对象以匹配特定字段：

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
expect(result).toHaveStructuredResponse();
expect(result).toHaveStructuredResponse({ name: "Alice", age: 30 });
```

### 匹配器参考

| 匹配器                                   | 描述                                                |
| ------------------------------------- | ------------------------------------------------- |
| `toBeHumanMessage(expected?)`         | 检查值是否为 `HumanMessage`。可选匹配内容（字符串）或字段（对象）。         |
| `toBeAIMessage(expected?)`            | 检查值是否为 `AIMessage`。可选匹配内容或字段。                     |
| `toBeSystemMessage(expected?)`        | 检查值是否为 `SystemMessage`。可选匹配内容或字段。                 |
| `toBeToolMessage(expected?)`          | 检查值是否为 `ToolMessage`。可选匹配内容或字段（如 `tool_call_id`）。 |
| `toHaveToolCalls(expected)`           | 检查 `AIMessage` 是否恰好包含给定的工具调用（顺序无关）。               |
| `toHaveToolCallCount(n)`              | 检查 `AIMessage` 是否恰好有 `n` 个工具调用。                   |
| `toContainToolCall(expected)`         | 检查 `AIMessage` 是否至少包含一个匹配的工具调用。支持 `.not`。         |
| `toHaveToolMessages(expected)`        | 检查消息数组是否按顺序包含给定的 `ToolMessage` 实例。                |
| `toHaveBeenInterrupted(value?)`       | 检查结果是否有 `__interrupt__`。可选匹配中断值。                  |
| `toHaveStructuredResponse(expected?)` | 检查结果是否有 `structuredResponse`。可选匹配特定字段。            |

## 降低成本和延迟

调用 LLM API 的集成测试会产生实际成本。以下几种做法有助于保持测试套件的快速和负担得起：

* **使用较小的模型**：对于仅需验证工具调用和响应结构的测试，使用 `gemini-3.1-flash-lite-preview` 或等效模型。
* **设置 `maxTokens`**：限制响应长度，避免冗长且昂贵的补全。
* **限制测试范围**：每个测试只测试一种行为。当单轮测试足够时，避免使用需要多次 LLM 调用的端到端场景。
* **选择性运行**：利用[上文](#separate-unit-and-integration-tests)的测试分离，仅在 CI 或部署前运行集成测试，而不是每次保存文件时都运行。

```ts theme={"theme":{"light":"catppuccin-latte","dark":"catppuccin-mocha"}}
const agent = createAgent({
  model: "gemini-3.1-flash-lite-preview",
  tools: [getWeather],
  modelArgs: { maxTokens: 256 },
});
```

## 后续步骤

了解如何使用确定性匹配或 LLM-as-judge 评估器在 [Evals](/oss/javascript/langchain/test/evals) 中评估智能体轨迹。

***

<div className="source-links">
  <Callout icon="edit">
    [Edit this page on GitHub](https://github.com/langchain-ai/docs/edit/main/src/i18n\zh-CN\oss\langchain\test\integration-testing.mdx) or [file an issue](https://github.com/langchain-ai/docs/issues/new/choose).
  </Callout>

  <Callout icon="terminal-2">
    [Connect these docs](/use-these-docs) to Claude, VSCode, and more via MCP for real-time answers.
  </Callout>
</div>
