跳转到内容

插件测试

OpenClaw 插件的测试工具、模式和 Lint 强制执行的参考。

这些测试助手子路径是 OpenClaw 自己的捆绑插件测试的仓库本地源入口点。它们不是第三方插件的包导出。

插件 API 模拟导入: APIopenclaw/plugin-sdk/plugin-test-api

代理运行时合约导入: openclaw/plugin-sdk/agent-runtime-test-contracts

通道合约导入: openclaw/plugin-sdk/channel-contract-testing

通道测试助手导入: openclaw/plugin-sdk/channel-test-helpers

通道目标测试导入: openclaw/plugin-sdk/channel-target-testing

插件合约导入: openclaw/plugin-sdk/plugin-test-contracts

插件运行时测试导入: openclaw/plugin-sdk/plugin-test-runtime

提供商合约导入: openclaw/plugin-sdk/provider-test-contracts

提供商 HTTP 模拟导入: openclaw/plugin-sdk/provider-http-test-mocks

环境/网络测试导入: openclaw/plugin-sdk/test-env

通用装置导入: openclaw/plugin-sdk/test-fixtures

Node 内置模拟导入: openclaw/plugin-sdk/test-node-mocks

对于新的插件测试,请优先使用下面特定的子路径。广泛的 openclaw/plugin-sdk/testing 桶仅用于旧版兼容性。仓库防护机制拒绝从 plugin-sdk/testingplugin-sdk/test-utils 进行新的真实导入;这些名称仅保留为兼容性记录测试的已弃用兼容性表面。

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";
导出用途
createTestPluginApi构建最小的插件 API 模拟,用于直接注册单元测试。从 APIplugin-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构建模拟的 CLI/插件运行时环境。从 plugin-sdk/plugin-test-runtime 导入
createPluginSetupWizardStatus为渠道插件构建设置状态辅助器。从 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在 media helper 测试中捕获 JSON fetch 请求。从 plugin-sdk/test-env 导入
withServer针对一次性本地 HTTP 服务器运行测试。从 plugin-sdk/test-env 导入
createMockIncomingRequest构建一个最小的传入 HTTP 请求对象。从 plugin-sdk/test-env 导入
withFetchPreconnect在安装了 preconnect hooks 的情况下运行 fetch 测试。从 plugin-sdk/test-env 导入
withEnv / withEnvAsync临时修补环境变量。从 plugin-sdk/test-env 导入
createTempHomeEnv / withTempHome / withTempDir创建隔离的文件系统测试装置。从 plugin-sdk/test-env 导入
createMockServerResponse创建一个最小的 HTTP 服务器响应 mock。从 plugin-sdk/test-env 导入
createCliRuntimeCapture在测试中捕获 CLI 运行时输出。从 plugin-sdk/test-fixtures 导入
importFreshModule导入带有新查询令牌的 ESM 模块以绕过模块缓存。从 plugin-sdk/test-fixtures 导入
bundledPluginRoot / bundledPluginFile解析捆绑插件源码或 dist 装置路径。从 plugin-sdk/test-fixtures 导入
mockNodeBuiltinModule安装狭窄的 Node 内置 Vitest mocks。从 plugin-sdk/test-node-mocks 导入
createSandboxTestContext构建沙盒测试上下文。从 plugin-sdk/test-fixtures 导入
writeSkill编写技能装置。从 plugin-sdk/test-fixtures 导入
makeAgentAssistantMessage构建 Agent 传输消息装置。从 plugin-sdk/test-fixtures 导入
peekSystemEvents / resetSystemEventsForTest检查并重置系统事件装置。从 plugin-sdk/test-fixtures 导入
sanitizeTerminalText清理终端输出以用于断言。从 plugin-sdk/test-fixtures 导入
countLines / hasBalancedFences断言分块输出形状。从 plugin-sdk/test-fixtures 导入
runProviderCatalog使用测试依赖项执行提供商目录钩子
resolveProviderWizardOptions解析合约测试中的提供商设置向导选项
resolveProviderModelPickerEntries解析合约测试中的提供商模型选择器条目
buildProviderPluginMethodChoice构建提供商向导选项 ID 以进行断言
setProviderWizardProvidersResolverForTest为隔离测试注入提供商向导提供商
createProviderUsageFetch构建提供商使用获取装置
useFrozenTime / useRealTime冻结和恢复计时器以进行时间敏感测试。从 plugin-sdk/test-env 导入
createTestWizardPrompter构建模拟的设置向导提示器
createRuntimeTaskFlow创建隔离的运行时任务流状态
typedCases保留表驱动测试的字面量类型。从 plugin-sdk/test-fixtures 导入

捆绑插件合约套件也使用 SDK 测试子路径来处理仅测试的注册表、清单、公共工件和运行时装置助手。依赖于捆绑 OpenClaw 清单的核心专用套件保留在 OpenClawsrc/plugins/contracts 下。 请将新的扩展测试放在文档化的专注 SDK 子路径上,例如 plugin-sdk/plugin-test-apiplugin-sdk/channel-contract-testingplugin-sdk/agent-runtime-test-contractsplugin-sdk/channel-test-helpersplugin-sdk/plugin-test-contractsplugin-sdk/plugin-test-runtimeplugin-sdk/provider-test-contractsplugin-sdk/provider-http-test-mocksplugin-sdk/test-envplugin-sdk/test-fixtures,而不是直接导入 广泛的 plugin-sdk/testing 兼容性桶、仓库 src/** 文件或仓库 test/helpers/* 桥接。

专注的测试子路径还会重新导出测试文件中有用的类型:

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 的加载器接受检查。请为您的插件依赖的每个注册表面 (尤其是 hooks 和 memory 等独占功能)至少添加一个由加载器支持的冒烟测试。

当缺少必需的元数据或插件调用了其不拥有的功能 API 时, 真正的加载器会导致插件注册失败。例如, api.registerHook(...) 需要一个 hook 名称, 而 api.registerMemoryCapability(...) 要求插件清单或导出的 入口声明 kind: "memory"

在测试捆绑的渠道插件时,请首选 openclaw/plugin-sdk/channel-test-helpers 中的 共享插件运行时 mock。其已弃用的 runtime.config.loadConfig()runtime.config.writeConfigFile(...) mocks 默认会抛出错误,以便测试捕获 对兼容性 的新使用。仅当测试明确涵盖旧版兼容性行为时, 才覆盖这些 mocks。

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();

首选逐实例存根而非原型变更:

// Preferred: per-instance stub
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });
// Avoid: prototype mutation
// MyChannelClient.prototype.sendMessage = vi.fn();

捆绑插件具有验证注册所有权的契约测试:

Terminal window
pnpm test -- src/plugins/contracts/

这些测试断言:

  • 哪些插件注册了哪些提供商
  • 哪些插件注册了哪些语音提供商
  • 注册形状的正确性
  • 运行时契约合规性

对于特定插件:

Terminal window
pnpm test -- <bundled-plugin-root>/my-channel/

仅运行契约测试:

Terminal window
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

对于仓库内插件,pnpm check 强制执行三项规则:

  1. 禁止单体根导入 — 拒绝 openclaw/plugin-sdk 根 barrel
  2. 禁止直接 src/ 导入 — 插件不能直接导入 ../../src/
  3. 禁止自导入 — 插件不能导入自己的 plugin-sdk/<name> 子路径

外部插件不受这些 lint 规则的约束,但建议遵循相同的模式。

OpenClaw 使用 Vitest 和 V8 覆盖率阈值。对于插件测试:

Terminal window
# 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

如果本地运行导致内存压力:

Terminal window
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test