测试
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 { createOpenClawTestState } from "openclaw/plugin-sdk/test-state";
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,
runDirectImportSmoke,
typedCases,
} from "openclaw/plugin-sdk/test-fixtures";
import { mockNodeBuiltinModule } from "openclaw/plugin-sdk/test-node-mocks";
对于捆绑插件测试,请使用这些聚焦的子路径。旧的 openclaw/plugin-sdk/testing barrel 是仓库本地的,未包含在发布包中,并且已被移除。旧的 openclaw/plugin-sdk/test-utils 别名也随之移除。pnpm run lint:plugins:no-extension-test-core-imports(scripts/check-no-extension-test-core-imports.ts)确保扩展测试使用上述聚焦的测试子路径。
捆绑的通道集成测试可以使用 agent-runtime-test-contracts 获取真实会话和订阅者测试夹具,使用 reply-payload-testing 进行 payload 构建和投递结算,并使用 plugin-test-runtime 处理 hook 运行器和注册表。这些辅助函数会复用其核心所有者;请显式注册会话测试夹具生命周期。当已发布的运行时子路径已经暴露所需操作时,请使用这些子路径。
请从 channel-ingress-test-runtime 或 plugin-state-test-runtime 等待 listChannelIngressQueueAccountIdsForTests。它使用共享的只读 worker,并且不会创建缺失的状态。在删除测试夹具的状态目录之前,请等待异步数据库清理完成。
对于直接 worker 测试夹具,请将 process-runtime 中的 resolveRuntimeWorkerUrl 与 test-env 中的 resolveRuntimeWorkerThreadExecArgv 配对使用。这可使源码 worker 和构建后的 worker 都使用运行时所有者的启动参数。
可用导出¶
| 导出 | 用途 |
|---|---|
createTestPluginApi |
构建用于直接注册单元测试的最小插件 API 模拟。从 plugin-sdk/plugin-test-api 导入 |
AUTH_PROFILE_RUNTIME_CONTRACT |
用于原生代理运行时适配器的共享 auth-profile 契约测试夹具。从 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 |
安装通道出站 payload 契约测试用例。从 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 导入 |
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 导入 |
createPluginRuntimeMock |
构建模拟的插件运行时接口。从 plugin-sdk/plugin-test-runtime 导入 |
createPluginSetupWizardStatus |
为渠道插件构建设置状态辅助函数。从 plugin-sdk/plugin-test-runtime 导入 |
createTestWizardPrompter |
构建模拟的设置向导提示器。从 plugin-sdk/plugin-test-runtime 导入 |
runProviderCatalog |
使用测试依赖执行提供商目录钩子。从 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 导入 |
| 导出 | 用途 |
|---|---|
createOpenClawTestState / withOpenClawTestState / OpenClawTestState |
创建并清理隔离的 OpenClaw 状态、配置、工作区、环境和认证配置文件测试夹具。从 plugin-sdk/test-state 导入 |
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 导入 |
createPluginMetadataSnapshotFixture |
构建包含对齐的 manifest 和已安装插件视图的完整元数据快照。从 plugin-sdk/plugin-test-runtime 导入 |
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 导入 |
createStagedInputOwnershipFixture |
创建所有者暂存的附件文件和未拥有的相似文件。从 plugin-sdk/test-env 导入 |
createMockServerResponse |
创建最小 HTTP 服务器响应模拟。从 plugin-sdk/test-env 导入 |
createProviderUsageFetch |
构建提供商用量 fetch 测试夹具。从 plugin-sdk/test-env 导入 |
useFrozenTime / useRealTime |
冻结并恢复计时器,用于时间敏感测试。从 plugin-sdk/test-env 导入 |
createCliRuntimeCapture |
在测试中捕获 CLI 运行时输出。从 plugin-sdk/test-fixtures 导入 |
findSourceImportBackedges |
异步检查仓库源代码静态导入闭包中的禁止依赖。从 plugin-sdk/test-fixtures 导入 |
runDirectImportSmoke |
在隔离的 Node 进程中运行插件公共表面导入。从 plugin-sdk/test-fixtures 导入 |
importFreshModule |
使用新的查询 token 导入 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 |
写入技能测试夹具。从 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 下。
对于频道账户策略测试,来自
openclaw/plugin-sdk/channel-test-helpers 的 createAccountPolicyInheritanceCases()
每次调用都会返回四个字面量继承行,并带有全新的对象和数组,同时保留被省略的策略字段。
请将其与 validateTestChannelConfig(channelId, channelConfig) 一起使用,后者
通过宿主配置边界验证经 schema 解析的频道数据。每个
插件测试仍负责其 schema 解析、账户解析器和断言,
包括检查被省略的账户策略保持不存在。
对于完整的零用量输入,来自
openclaw/plugin-sdk/test-fixtures 的 createZeroUsageFixture()
会返回全新的用量和嵌套成本对象,且不含可选遥测字段。请显式保留预期用量值。
类型¶
聚焦测试子路径还会重新导出在测试文件中有用的类型:
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 模拟传递给 register(api) 的单元测试不会验证
OpenClaw 加载器的验收门控。请为插件依赖的每个注册面至少添加一个由加载器支持的
冒烟测试,尤其是钩子和独占能力(例如 memory)。
当必需元数据缺失,或插件调用了它不拥有的能力 API 时,
真实加载器会使插件注册失败。例如,
api.registerHook(...) 需要钩子名称,而
api.registerMemoryCapability(...) 要求插件清单或导出入口声明 kind: "memory"。
测试运行时配置访问¶
优先使用来自
openclaw/plugin-sdk/plugin-test-runtime 的共享插件运行时模拟。其运行时配置辅助函数模拟当前的
快照和变更 API。
单元测试频道插件¶
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");
});
});
单元测试提供方插件¶
对于解析提供方端点能力的捆绑目录测试,请在文件或套件作用域中从 openclaw/plugin-sdk/plugin-test-runtime 调用 useProviderCatalogMetadata(new URL(".", import.meta.url))。它会一次性准备插件的清单元数据,在每个测试前后安装并清除该快照,并在断言期间拒绝 Jiti 加载。这样可以在不改变提供方行为的情况下,避免冷运行时发现进入目录测试截止时间。
当某个用例测试其他提供方的端点时,请传入额外的清单根目录,例如 useProviderCatalogMetadata(new URL(".", import.meta.url), new URL("../google/", import.meta.url))。在路由特定用例中断言端点类型,以免缺失的元数据将提供方路由变成非预期的自定义端点用例。
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();
契约测试(仓库内插件)¶
捆绑插件具有契约测试,用于验证注册所有权:
这些测试断言:
- 哪些插件注册哪些提供方
- 哪些插件注册哪些语音提供方
- 注册结构正确性
- 运行时契约合规性
运行范围限定测试¶
针对特定插件:
仅运行契约测试:
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.mts 会在 CI 中运行一组 lint:plugins:* 导入边界检查;每个检查也可以在本地独立运行:
| 命令 | 强制约束 |
|---|---|
pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports |
捆绑插件不能导入单体 openclaw/plugin-sdk 根 barrel。 |
pnpm run lint:plugins:no-extension-src-imports |
生产扩展文件不能直接导入仓库的 src/** 树(../../src/...)。 |
pnpm run lint:plugins:no-extension-test-core-imports |
扩展测试文件不能导入已移除的 SDK 测试别名或其他仅限核心的测试辅助函数。 |
外部插件不受这些 lint 规则约束,但建议遵循相同的模式。
测试配置¶
OpenClaw 使用 Vitest 5,并带有信息性的 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
如果本地运行导致内存压力:
相关¶
本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw