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

# 外掛測試

OpenClaw 外掛的測試公用程式、模式與 lint 強制規則參考。

<Tip>
  **正在尋找測試範例嗎？** 操作指南包含完整的測試範例：
  [頻道外掛測試](/zh-TW/plugins/sdk-channel-plugins#step-6-test)與
  [提供者外掛測試](/zh-TW/plugins/sdk-provider-plugins#step-6-test)。
</Tip>

## 測試公用程式

這些子路徑是 OpenClaw 自有內建外掛測試在存放庫本機使用的原始碼進入點。它們並非為第三方外掛發布的 `package.json` 匯出，且可能會匯入 Vitest 或其他僅限存放庫使用的測試相依套件。

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import {
  shouldAckReaction,
  removeAckReactionAfterReply,
} from "openclaw/plugin-sdk/channel-feedback";
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/channel-target-testing";
import { AUTH_PROFILE_RUNTIME_CONTRACT } from "openclaw/plugin-sdk/agent-runtime-test-contracts";
import { createTestPluginApi } from "openclaw/plugin-sdk/plugin-test-api";
import { expectChannelInboundContextContract } from "openclaw/plugin-sdk/channel-contract-testing";
import { createStartAccountContext } from "openclaw/plugin-sdk/channel-test-helpers";
import { describePluginRegistrationContract } from "openclaw/plugin-sdk/plugin-test-contracts";
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { describeOpenAIProviderRuntimeContract } from "openclaw/plugin-sdk/provider-test-contracts";
import { getProviderHttpMocks } from "openclaw/plugin-sdk/provider-http-test-mocks";
import { withEnv, withFetchPreconnect, withServer } from "openclaw/plugin-sdk/test-env";
import { isLiveTestEnabled } from "openclaw/plugin-sdk/test-live";
import { createRequestCaptureJsonFetch } from "openclaw/plugin-sdk/test-media-understanding";
import {
  bundledPluginRoot,
  createCliRuntimeCapture,
  typedCases,
} from "openclaw/plugin-sdk/test-fixtures";
import { mockNodeBuiltinModule } from "openclaw/plugin-sdk/test-node-mocks";
```

內建外掛測試請使用這些聚焦的子路徑。先前的 `openclaw/plugin-sdk/testing` 彙總入口僅供存放庫本機使用，未包含在發布的套件中，現已移除。先前的 `openclaw/plugin-sdk/test-utils` 別名也隨之移除。`pnpm run lint:plugins:no-extension-test-core-imports`（`scripts/check-no-extension-test-core-imports.ts`）會讓擴充功能測試繼續使用上述聚焦的測試子路徑。

### 可用的匯出

| 匯出項目                                                 | 用途                                                                                          |
| ---------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `createTestPluginApi`                                | 建立最小化的外掛 API 模擬，用於直接註冊單元測試。從 `plugin-sdk/plugin-test-api` 匯入                                |
| `AUTH_PROFILE_RUNTIME_CONTRACT`                      | 原生代理程式執行階段介面卡共用的驗證設定檔契約測試資料。從 `plugin-sdk/agent-runtime-test-contracts` 匯入                  |
| `DELIVERY_NO_REPLY_RUNTIME_CONTRACT`                 | 原生代理程式執行階段介面卡共用的傳遞抑制契約測試資料。從 `plugin-sdk/agent-runtime-test-contracts` 匯入                   |
| `OUTCOME_FALLBACK_RUNTIME_CONTRACT`                  | 原生代理程式執行階段介面卡共用的後援分類契約測試資料。從 `plugin-sdk/agent-runtime-test-contracts` 匯入                   |
| `createParameterFreeTool`                            | 建立動態工具結構描述測試資料，用於原生執行階段契約測試。從 `plugin-sdk/agent-runtime-test-contracts` 匯入                  |
| `expectChannelInboundContextContract`                | 斷言頻道傳入內容的形狀。從 `plugin-sdk/channel-contract-testing` 匯入                                      |
| `installChannelOutboundPayloadContractSuite`         | 安裝頻道傳出承載資料契約測試案例。從 `plugin-sdk/channel-contract-testing` 匯入                                 |
| `createStartAccountContext`                          | 建立頻道帳號生命週期內容。從 `plugin-sdk/channel-test-helpers` 匯入                                         |
| `installChannelActionsContractSuite`                 | 安裝通用頻道訊息動作契約測試案例。從 `plugin-sdk/channel-test-helpers` 匯入                                     |
| `installChannelSetupContractSuite`                   | 安裝通用頻道設定契約測試案例。從 `plugin-sdk/channel-test-helpers` 匯入                                       |
| `installChannelStatusContractSuite`                  | 安裝通用頻道狀態契約測試案例。從 `plugin-sdk/channel-test-helpers` 匯入                                       |
| `expectDirectoryIds`                                 | 從目錄清單函式斷言頻道目錄 ID。從 `plugin-sdk/channel-test-helpers` 匯入                                     |
| `assertBundledChannelEntries`                        | 斷言隨附頻道進入點公開預期的公用契約。從 `plugin-sdk/channel-test-helpers` 匯入                                   |
| `formatEnvelopeTimestamp`                            | 格式化具確定性的封套時間戳記。從 `plugin-sdk/channel-test-helpers` 匯入                                       |
| `expectPairingReplyText`                             | 斷言頻道配對回覆文字並擷取其代碼。從 `plugin-sdk/channel-test-helpers` 匯入                                     |
| `describePluginRegistrationContract`                 | 安裝外掛註冊契約檢查。從 `plugin-sdk/plugin-test-contracts` 匯入                                          |
| `registerSingleProviderPlugin`                       | 在載入器冒煙測試中註冊一個供應商外掛。從 `plugin-sdk/plugin-test-runtime` 匯入                                    |
| `registerProviderPlugin`                             | 從一個外掛擷取所有供應商種類。從 `plugin-sdk/plugin-test-runtime` 匯入                                        |
| `registerProviderPlugins`                            | 擷取多個外掛的供應商註冊項目。從 `plugin-sdk/plugin-test-runtime` 匯入                                        |
| `requireRegisteredProvider`                          | 斷言供應商集合包含某個 ID。從 `plugin-sdk/plugin-test-runtime` 匯入                                        |
| `createRuntimeEnv`                                   | 建立模擬的命令列介面／外掛執行階段環境。從 `plugin-sdk/plugin-test-runtime` 匯入                                   |
| `createPluginRuntimeMock`                            | 建立模擬的外掛執行階段介面。從 `plugin-sdk/plugin-test-runtime` 匯入                                         |
| `createPluginSetupWizardStatus`                      | 為頻道外掛建立設定狀態輔助工具。從 `plugin-sdk/plugin-test-runtime` 匯入                                       |
| `createTestWizardPrompter`                           | 建立模擬的設定精靈提示器。從 `plugin-sdk/plugin-test-runtime` 匯入                                          |
| `createRuntimeTaskFlow`                              | 建立隔離的執行階段 TaskFlow 狀態。從 `plugin-sdk/plugin-test-runtime` 匯入                                 |
| `runProviderCatalog`                                 | 使用測試相依項目執行供應商目錄鉤子。從 `plugin-sdk/plugin-test-runtime` 匯入                                     |
| `resolveProviderWizardOptions`                       | 在契約測試中解析供應商設定精靈選項。從 `plugin-sdk/plugin-test-runtime` 匯入                                     |
| `resolveProviderModelPickerEntries`                  | 在契約測試中解析供應商模型選擇器項目。從 `plugin-sdk/plugin-test-runtime` 匯入                                    |
| `buildProviderPluginMethodChoice`                    | 建立供應商精靈選項 ID 以供斷言。從 `plugin-sdk/plugin-test-runtime` 匯入                                     |
| `setProviderWizardProvidersResolverForTest`          | 為隔離測試注入供應商精靈的供應商。從 `plugin-sdk/plugin-test-runtime` 匯入                                      |
| `describeOpenAIProviderRuntimeContract`              | 安裝供應商系列執行階段契約檢查。從 `plugin-sdk/provider-test-contracts` 匯入                                   |
| `expectPassthroughReplayPolicy`                      | 斷言供應商重播原則會傳遞供應商自有的工具與中繼資料。從 `plugin-sdk/provider-test-contracts` 匯入                         |
| `runRealtimeSttLiveTest`                             | 使用共用音訊測試資料執行即時 STT 供應商實機測試。從 `plugin-sdk/provider-test-contracts` 匯入                        |
| `normalizeTranscriptForMatch`                        | 在模糊斷言前正規化實機轉錄輸出。從 `plugin-sdk/provider-test-contracts` 匯入                                   |
| `expectExplicitVideoGenerationCapabilities`          | 斷言影片供應商明確宣告生成模式功能。從 `plugin-sdk/provider-test-contracts` 匯入                                 |
| `expectExplicitMusicGenerationCapabilities`          | 斷言音樂供應商明確宣告生成／編輯功能。從 `plugin-sdk/provider-test-contracts` 匯入                                |
| `mockSuccessfulDashscopeVideoTask`                   | 安裝成功的 DashScope 相容影片任務回應。從 `plugin-sdk/provider-test-contracts` 匯入                          |
| `getProviderHttpMocks`                               | 存取選擇啟用的供應商 HTTP／驗證 Vitest 模擬。從 `plugin-sdk/provider-http-test-mocks` 匯入                     |
| `installProviderHttpMockCleanup`                     | 在每項測試後重設供應商 HTTP／驗證模擬。從 `plugin-sdk/provider-http-test-mocks` 匯入                            |
| `installCommonResolveTargetErrorCases`               | 目標解析錯誤處理的共用測試案例。從 `plugin-sdk/channel-target-testing` 匯入                                    |
| `shouldAckReaction`                                  | 檢查頻道是否應新增確認反應。從 `plugin-sdk/channel-feedback` 匯入                                            |
| `removeAckReactionAfterReply`                        | 回覆傳遞後移除確認反應。從 `plugin-sdk/channel-feedback` 匯入                                              |
| `createTestRegistry`                                 | 建立頻道外掛登錄測試資料。從 `plugin-sdk/plugin-test-runtime` 或 `plugin-sdk/channel-test-helpers` 匯入      |
| `createEmptyPluginRegistry`                          | 建立空的外掛登錄測試資料。從 `plugin-sdk/plugin-test-runtime` 或 `plugin-sdk/channel-test-helpers` 匯入      |
| `setActivePluginRegistry`                            | 為外掛執行階段測試安裝登錄測試資料。從 `plugin-sdk/plugin-test-runtime` 或 `plugin-sdk/channel-test-helpers` 匯入 |
| `createRequestCaptureJsonFetch`                      | 在媒體輔助工具測試中擷取 JSON fetch 請求。從 `plugin-sdk/test-media-understanding` 匯入                       |
| `isLiveTestEnabled`                                  | 控制選擇啟用的實機供應商測試。從 `plugin-sdk/test-live` 匯入                                                  |
| `collectProviderApiKeys`                             | 探索實機供應商測試的認證資訊。從 `plugin-sdk/test-live-auth` 匯入                                             |
| `parseProviderModelMap`                              | 剖析音樂／影片實機測試的模型覆寫值。從 `plugin-sdk/test-media-generation` 匯入                                   |
| `withServer`                                         | 對可拋棄的本機 HTTP 伺服器執行測試。從 `plugin-sdk/test-env` 匯入                                             |
| `createMockIncomingRequest`                          | 建立最小化的傳入 HTTP 請求物件。從 `plugin-sdk/test-env` 匯入                                               |
| `withFetchPreconnect`                                | 執行已安裝預連線鉤子的 fetch 測試。從 `plugin-sdk/test-env` 匯入                                             |
| `withEnv` / `withEnvAsync`                           | 暫時修補環境變數。從 `plugin-sdk/test-env` 匯入                                                         |
| `createTempHomeEnv` / `withTempHome` / `withTempDir` | 建立隔離的檔案系統測試資料。從 `plugin-sdk/test-env` 匯入                                                    |
| `createMockServerResponse`                           | 建立最小化的 HTTP 伺服器回應模擬。從 `plugin-sdk/test-env` 匯入                                              |
| `createProviderUsageFetch`                           | 建立供應商用量 fetch 測試資料。從 `plugin-sdk/test-env` 匯入                                               |
| `useFrozenTime` / `useRealTime`                      | 凍結並還原計時器，用於時間敏感的測試。從 `plugin-sdk/test-env` 匯入                                               |
| `createCliRuntimeCapture`                            | 在測試中擷取命令列介面執行階段輸出。從 `plugin-sdk/test-fixtures` 匯入                                           |
| `importFreshModule`                                  | 使用新的查詢權杖匯入 ESM 模組，以略過模組快取。從 `plugin-sdk/test-fixtures` 匯入                                   |
| `bundledPluginRoot` / `bundledPluginFile`            | 解析隨附外掛的原始碼或 dist 測試資料路徑。從 `plugin-sdk/test-fixtures` 匯入                                     |
| `mockNodeBuiltinModule`                              | 安裝範圍有限的 Node 內建 Vitest 模擬。從 `plugin-sdk/test-node-mocks` 匯入                                 |
| `createSandboxTestContext`                           | 建立沙箱測試內容。從 `plugin-sdk/test-fixtures` 匯入                                                    |
| `writeSkill`                                         | 寫入 Skills 測試資料。從 `plugin-sdk/test-fixtures` 匯入                                              |
| `makeAgentAssistantMessage`                          | 建立代理程式轉錄訊息測試資料。從 `plugin-sdk/test-fixtures` 匯入                                              |
| `peekSystemEvents` / `resetSystemEventsForTest`      | 檢查並重設系統事件測試資料。從 `plugin-sdk/test-fixtures` 匯入                                               |
| `sanitizeTerminalText`                               | 清理終端輸出以供斷言。從 `plugin-sdk/test-fixtures` 匯入                                                  |
| `countLines` / `hasBalancedFences`                   | 斷言分塊輸出的形狀。從 `plugin-sdk/test-fixtures` 匯入                                                   |
| `typedCases`                                         | 保留表格驅動測試的常值型別。從 `plugin-sdk/test-fixtures` 匯入                                               |

內建外掛的合約測試套件也會使用這些 SDK 測試子路徑，取得僅供測試使用的登錄檔、資訊清單、公開成品與執行階段固定資料輔助工具。
依賴內建 OpenClaw 清單的僅限核心測試套件則仍放在
`src/plugins/contracts` 下。

### 型別

聚焦測試的子路徑也會重新匯出測試檔案中實用的型別：

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import type {
  ChannelAccountSnapshot,
  ChannelGatewayContext,
} from "openclaw/plugin-sdk/channel-contract";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts";
import type { MockFn, PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk/plugin-test-runtime";
```

## 測試目標解析

使用 `installCommonResolveTargetErrorCases` 為頻道目標解析新增標準錯誤案例：

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { describe } from "vitest";
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/channel-target-testing";

describe("my-channel 目標解析", () => {
  installCommonResolveTargetErrorCases({
    resolveTarget: ({ to, mode, allowFrom }) => {
      // 你的頻道目標解析邏輯
      return myChannelResolveTarget({ to, mode, allowFrom });
    },
    implicitAllowFrom: ["user1", "user2"],
  });

  // 新增頻道特定的測試案例
  it("應解析 @username 目標", () => {
    // ...
  });
});
```

## 測試模式

### 測試註冊合約

將手寫的 `api` 模擬物件傳給 `register(api)` 的單元測試，不會
執行 OpenClaw 的載入器接受閘門。你的外掛所依賴的每個註冊介面都應至少新增一個由載入器支援的
冒煙測試，尤其是鉤子與記憶等專屬功能。

若缺少必要的中繼資料，或外掛呼叫不屬於自己的功能 API，真正的載入器會使外掛註冊失敗。例如，
`api.registerHook(...)` 需要鉤子名稱，而
`api.registerMemoryCapability(...)` 要求外掛資訊清單或匯出的
進入點宣告 `kind: "memory"`。

### 測試執行階段設定存取

優先使用
`openclaw/plugin-sdk/plugin-test-runtime` 的共用外掛執行階段模擬物件。其執行階段設定輔助工具會模擬
目前的快照與變更 API。

### 對頻道外掛進行單元測試

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { describe, it, expect, vi } from "vitest";

describe("my-channel 外掛", () => {
  it("應從設定解析帳號", () => {
    const cfg = {
      channels: {
        "my-channel": {
          token: "test-token",
          allowFrom: ["user1"],
        },
      },
    };

    const account = myPlugin.setup.resolveAccount(cfg, undefined);
    expect(account.token).toBe("test-token");
  });

  it("應檢查帳號而不具現化機密", () => {
    const cfg = {
      channels: {
        "my-channel": { token: "test-token" },
      },
    };

    const inspection = myPlugin.setup.inspectAccount(cfg, undefined);
    expect(inspection.configured).toBe(true);
    expect(inspection.tokenStatus).toBe("available");
    // 未公開權杖值
    expect(inspection).not.toHaveProperty("token");
  });
});
```

### 對供應商外掛進行單元測試

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { describe, it, expect } from "vitest";

describe("my-provider 外掛", () => {
  it("應解析動態模型", () => {
    const model = myProvider.resolveDynamicModel({
      modelId: "custom-model-v2",
      // ... 情境
    });

    expect(model.id).toBe("custom-model-v2");
    expect(model.provider).toBe("my-provider");
    expect(model.api).toBe("openai-completions");
  });

  it("當 API 金鑰可用時應傳回目錄", async () => {
    const result = await myProvider.catalog.run({
      resolveProviderApiKey: () => ({ apiKey: "test-key" }),
      // ... 情境
    });

    expect(result?.provider?.models).toHaveLength(2);
  });
});
```

### 模擬外掛執行階段

對於使用 `createPluginRuntimeStore` 的程式碼，請在測試中模擬執行階段：

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";

const store = createPluginRuntimeStore<PluginRuntime>({
  pluginId: "test-plugin",
  errorMessage: "測試執行階段尚未設定",
});

// 在測試設定中
const mockRuntime = {
  agent: {
    resolveAgentDir: vi.fn().mockReturnValue("/tmp/agent"),
    // ... 其他模擬物件
  },
  config: {
    current: vi.fn(() => ({}) as const),
    mutateConfigFile: vi.fn(),
    replaceConfigFile: vi.fn(),
  },
  // ... 其他命名空間
} as unknown as PluginRuntime;

store.setRuntime(mockRuntime);

// 測試後
store.clearRuntime();
```

### 使用個別執行個體的虛設常式進行測試

優先使用個別執行個體的虛設常式，而不是修改原型：

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
// 建議：個別執行個體的虛設常式
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });

// 避免：修改原型
// MyChannelClient.prototype.sendMessage = vi.fn();
```

## 合約測試（存放於儲存庫內的外掛）

內建外掛具有驗證註冊所有權的合約測試：

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
pnpm test src/plugins/contracts/
```

這些測試會斷言：

* 哪些外掛註冊哪些供應商
* 哪些外掛註冊哪些語音供應商
* 註冊形式的正確性
* 執行階段合約遵循情況

### 執行限定範圍的測試

針對特定外掛：

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
pnpm test <bundled-plugin-root>/my-channel/
```

僅執行合約測試：

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
pnpm test src/plugins/contracts/shape.contract.test.ts
pnpm test src/plugins/contracts/auth-choice.contract.test.ts
pnpm test src/plugins/contracts/runtime-seams.contract.test.ts
```

## Lint 強制檢查（存放於儲存庫內的外掛）

`scripts/run-additional-boundary-checks.mjs` 會在 CI 中執行一組 `lint:plugins:*`
匯入邊界檢查；每項檢查也可以在本機獨立執行：

| 命令                                                             | 強制規則                                                  |
| -------------------------------------------------------------- | ----------------------------------------------------- |
| `pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports` | 內建外掛不得匯入單體式的 `openclaw/plugin-sdk` 根彙總模組。             |
| `pnpm run lint:plugins:no-extension-src-imports`               | 正式環境的擴充功能檔案不得直接匯入儲存庫的 `src/**` 樹狀結構（`../../src/...`）。 |
| `pnpm run lint:plugins:no-extension-test-core-imports`         | 擴充功能測試檔案不得匯入已移除的 SDK 測試別名或其他僅限核心使用的測試輔助工具。            |

外部外掛不受這些 lint 規則約束，但建議遵循相同的
模式。

## 測試設定

OpenClaw 使用 Vitest 4，並提供資訊用途的 V8 覆蓋率報告。針對外掛測試：

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
# 執行所有測試
pnpm test

# 執行特定外掛測試
pnpm test <bundled-plugin-root>/my-channel/src/channel.test.ts

# 使用特定測試名稱篩選條件執行
pnpm test <bundled-plugin-root>/my-channel/ -t "resolves account"

# 執行並產生覆蓋率報告
pnpm test:coverage
```

若本機執行造成記憶體壓力：

```bash theme={"theme":{"light":"min-light","dark":"min-dark"}}
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test
```

## 相關內容

* [SDK 概觀](/zh-TW/plugins/sdk-overview) -- 匯入慣例
* [SDK 頻道外掛](/zh-TW/plugins/sdk-channel-plugins) -- 頻道外掛介面
* [SDK 供應商外掛](/zh-TW/plugins/sdk-provider-plugins) -- 供應商外掛鉤子
* [建置外掛](/zh-TW/plugins/building-plugins) -- 入門指南
