跳到正文
FunCoding

搜索

搜索文档、文章、Skill 和 MCP

插件测试

OpenClaw 插件的测试工具与模式

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 { 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构建模拟的 CLI/插件运行时环境。从 plugin-sdk/plugin-test-runtime 导入
createPluginRuntimeMock构建模拟的插件运行时接口。从 plugin-sdk/plugin-test-runtime 导入
createPluginSetupWizardStatus为渠道插件构建设置状态辅助函数。从 plugin-sdk/plugin-test-runtime 导入
createTestWizardPrompter构建模拟的设置向导提示器。从 plugin-sdk/plugin-test-runtime 导入
createRuntimeTaskFlow创建隔离的运行时 Task Flow 状态。从 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使用共享音频夹具运行实时语音转文本提供商的在线测试。从 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 获取请求。从 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在安装预连接钩子的情况下运行获取测试。从 plugin-sdk/test-env 导入
withEnv / withEnvAsync临时修补环境变量。从 plugin-sdk/test-env 导入
createTempHomeEnv / withTempHome / withTempDir创建隔离的文件系统测试夹具。从 plugin-sdk/test-env 导入
createMockServerResponse创建最小化的 HTTP 服务器响应模拟。从 plugin-sdk/test-env 导入
createProviderUsageFetch构建提供商用量获取夹具。从 plugin-sdk/test-env 导入
useFrozenTime / useRealTime为时间敏感型测试冻结并恢复计时器。从 plugin-sdk/test-env 导入
createCliRuntimeCapture在测试中捕获 CLI 运行时输出。从 plugin-sdk/test-fixtures 导入
importFreshModule使用新的查询令牌导入 ESM 模块以绕过模块缓存。从 plugin-sdk/test-fixtures 导入
bundledPluginRoot / bundledPluginFile解析内置插件源代码或分发版夹具路径。从 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 下。

类型

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

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 目标解析", () => {
  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。

对渠道插件进行单元测试

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");
  });
});

对提供商插件进行单元测试

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 的代码,在测试中模拟运行时:

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

使用每实例存根进行测试

优先使用每实例存根,而不是修改原型:

// 推荐:每实例存根
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });

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

契约测试(仓库内插件)

内置插件包含用于验证注册所有权的契约测试:

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 强制检查(仓库内插件)

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 覆盖率报告。对于插件测试:

# 运行所有测试
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

如果本地运行造成内存压力:

OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test

相关内容