跳转至

测试

OpenClaw 插件的测试工具、模式和 lint 强制检查参考。

Tip

正在查找测试示例? 操作指南包含完整测试示例: 通道插件测试 和 提供商插件测试。

测试工具

这些子路径是 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/

这些测试断言:

  • 哪些插件注册哪些提供方
  • 哪些插件注册哪些语音提供方
  • 注册结构正确性
  • 运行时契约合规性

运行范围限定测试

针对特定插件:

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.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

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

OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw