跳轉到主要內容
OpenClaw 外掛的測試工具、模式與 lint 強制規則參考。
在找測試範例嗎? 操作指南包含完整測試範例: 通道外掛測試提供者外掛測試

測試工具

這些子路徑是 OpenClaw 自有隨附外掛測試的儲存庫本機原始碼進入點。它們不是為第三方外掛發布的 package.json 匯出,且可能會匯入 Vitest 或其他僅限儲存庫使用的測試相依項。
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 {
  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-importsscripts/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為原生執行階段合約測試建立動態工具 schema 測試夾具。請從 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使用測試相依項執行提供者目錄 hook。請從 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-runtimeplugin-sdk/channel-test-helpers 匯入
createEmptyPluginRegistry建立空的外掛登錄測試夾具。請從 plugin-sdk/plugin-test-runtimeplugin-sdk/channel-test-helpers 匯入
setActivePluginRegistry為外掛執行階段測試安裝登錄測試夾具。請從 plugin-sdk/plugin-test-runtimeplugin-sdk/channel-test-helpers 匯入
createRequestCaptureJsonFetch在媒體輔助工具測試中擷取 JSON fetch 請求。請從 plugin-sdk/test-env 匯入
withServer針對可拋棄的本機 HTTP 伺服器執行測試。請從 plugin-sdk/test-env 匯入
createMockIncomingRequest建立最小傳入 HTTP 請求物件。請從 plugin-sdk/test-env 匯入
withFetchPreconnect安裝預連線 hook 後執行 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安裝範圍狹窄的節點內建 Vitest 模擬。請從 plugin-sdk/test-node-mocks 匯入
createSandboxTestContext建立沙箱測試內容情境。請從 plugin-sdk/test-fixtures 匯入
writeSkill寫入 skill fixtures。從 plugin-sdk/test-fixtures 匯入
makeAgentAssistantMessage建立 agent transcript message fixtures。從 plugin-sdk/test-fixtures 匯入
peekSystemEvents / resetSystemEventsForTest檢查並重設 system event fixtures。從 plugin-sdk/test-fixtures 匯入
sanitizeTerminalText清理終端機輸出以供斷言使用。從 plugin-sdk/test-fixtures 匯入
countLines / hasBalancedFences斷言 chunking output shape。從 plugin-sdk/test-fixtures 匯入
typedCases為表格驅動測試保留字面型別。從 plugin-sdk/test-fixtures 匯入
bundled 外掛契約套件也會使用這些 SDK 測試子路徑,作為 僅供測試的登錄檔、manifest、公開成品,以及執行階段 fixture 輔助工具。 只依賴 bundled OpenClaw 清單的核心專用套件則維持放在 src/plugins/contracts 底下。

型別

聚焦的測試子路徑也會重新匯出測試檔案中實用的型別:
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 來加入頻道目標解析的標準錯誤案例:
import { describe } from "vitest";
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/channel-target-testing";

describe("my-channel target resolution", () => {
  installCommonResolveTargetErrorCases({
    resolveTarget: ({ to, mode, allowFrom }) => {
      // Your channel's target resolution logic
      return myChannelResolveTarget({ to, mode, allowFrom });
    },
    implicitAllowFrom: ["user1", "user2"],
  });

  // Add channel-specific test cases
  it("should resolve @username targets", () => {
    // ...
  });
});

測試模式

測試註冊契約

將手寫的 api mock 傳給 register(api) 的單元測試,不會 執行 OpenClaw 的 loader 接受閘門。請為外掛依賴的每個註冊介面至少加入一個 由 loader 支援的煙霧測試,特別是 hooks 和 memory 這類獨占能力。 真正的 loader 會在必要 metadata 遺失,或外掛呼叫不屬於自己的能力 API 時, 讓外掛註冊失敗。例如,api.registerHook(...) 需要 hook 名稱,而 api.registerMemoryCapability(...) 需要外掛 manifest 或匯出的 entry 宣告 kind: "memory"

測試執行階段設定存取

偏好使用來自 openclaw/plugin-sdk/plugin-test-runtime 的共用外掛執行階段 mock。 它的 runtime.config.loadConfig()runtime.config.writeConfigFile(...) mock 預設會拋出錯誤,因此測試能捕捉到對已棄用相容性 API 的新使用。 只有在測試明確涵蓋舊版相容性行為時,才覆寫這些 mock。

單元測試頻道外掛

import { describe, it, expect, vi } from "vitest";

describe("my-channel plugin", () => {
  it("should resolve account from config", () => {
    const cfg = {
      channels: {
        "my-channel": {
          token: "test-token",
          allowFrom: ["user1"],
        },
      },
    };

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

  it("should inspect account without materializing secrets", () => {
    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");
    // No token value exposed
    expect(inspection).not.toHaveProperty("token");
  });
});

單元測試提供者外掛

import { describe, it, expect } from "vitest";

describe("my-provider plugin", () => {
  it("should resolve dynamic models", () => {
    const model = myProvider.resolveDynamicModel({
      modelId: "custom-model-v2",
      // ... context
    });

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

  it("should return catalog when API key is available", async () => {
    const result = await myProvider.catalog.run({
      resolveProviderApiKey: () => ({ apiKey: "test-key" }),
      // ... context
    });

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

模擬外掛執行階段

對於使用 createPluginRuntimeStore 的程式碼,請在測試中模擬執行階段:
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: "test runtime not set",
});

// In test setup
const mockRuntime = {
  agent: {
    resolveAgentDir: vi.fn().mockReturnValue("/tmp/agent"),
    // ... other mocks
  },
  config: {
    current: vi.fn(() => ({}) as const),
    mutateConfigFile: vi.fn(),
    replaceConfigFile: vi.fn(),
  },
  // ... other namespaces
} as unknown as PluginRuntime;

store.setRuntime(mockRuntime);

// After tests
store.clearRuntime();

使用每個執行個體的 stub 進行測試

偏好使用每個執行個體的 stub,而不是修改 prototype:
// Preferred: per-instance stub
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });

// Avoid: prototype mutation
// MyChannelClient.prototype.sendMessage = vi.fn();

契約測試(repo 內外掛)

Bundled 外掛有契約測試會驗證註冊所有權:
pnpm test src/plugins/contracts/
這些測試會斷言:
  • 哪些外掛註冊哪些提供者
  • 哪些外掛註冊哪些語音提供者
  • 註冊形狀正確性
  • 執行階段契約遵循狀況

執行限定範圍的測試

針對特定外掛:
pnpm test <bundled-plugin-root>/my-channel/
僅針對契約測試:
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 強制檢查(repo 內外掛)

scripts/run-additional-boundary-checks.mjs 會在 CI 中執行一組 lint:plugins:* 匯入邊界檢查;每項也都可以在本機獨立執行:
命令強制檢查
pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-importsBundled 外掛不能匯入整體式 openclaw/plugin-sdk 根 barrel。
pnpm run lint:plugins:no-extension-src-importsProduction extension 檔案不能直接匯入 repo src/** tree(../../src/...)。
pnpm run lint:plugins:no-extension-test-core-importsExtension 測試檔案不能匯入 openclaw/plugin-sdk/testingplugin-sdk/test-utils,或其他核心專用測試輔助工具。
外部外掛不受這些 lint 規則約束,但建議遵循相同模式。

測試設定

OpenClaw 使用 Vitest 4 搭配 V8 覆蓋率門檻。對於外掛測試:
# Run all tests
pnpm test

# Run specific plugin tests
pnpm test <bundled-plugin-root>/my-channel/src/channel.test.ts

# Run with a specific test name filter
pnpm test <bundled-plugin-root>/my-channel/ -t "resolves account"

# Run with coverage
pnpm test:coverage
如果本機執行造成記憶體壓力:
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test

相關