Zum Inhalt springen

Test OpenClaw Plugins: Utilities and Patterns Guide

Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.

Writing tests for plugins can feel like a chore, especially when you are dealing with complex integrations or external APIs. We have all been there—staring at a failing test suite because a mock did not behave or a target resolution failed. To make your life easier, OpenClaw provides a set of utilities and patterns designed to keep your plugin code reliable without the headache.

If you are looking for specific examples, the how-to guides include worked test examples for Channel plugin tests and Provider plugin tests.

You can find the main testing helpers in the openclaw/plugin-sdk/testing subpath. This exports a narrow set of tools to help you handle common plugin tasks.

import {
installCommonResolveTargetErrorCases,
shouldAckReaction,
removeAckReactionAfterReply,
} from "openclaw/plugin-sdk/testing";
ExportPurpose
installCommonResolveTargetErrorCasesShared test cases for target resolution error handling
shouldAckReactionCheck whether a channel should add an ack reaction
removeAckReactionAfterReplyRemove ack reaction after reply delivery

The testing subpath also re-exports types you will need in your test files:

import type {
ChannelAccountSnapshot,
ChannelGatewayContext,
OpenClawConfig,
PluginRuntime,
RuntimeEnv,
MockFn,
} from "openclaw/plugin-sdk/testing";

You should use installCommonResolveTargetErrorCases to add standard error cases for channel target resolution. This ensures your channel handles edge cases consistently.

import { describe } from "vitest";
import { installCommonResolveTargetErrorCases } from "openclaw/plugin-sdk/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", () => {
// ...
});
});

When testing a channel plugin, you want to verify that accounts resolve correctly from the configuration and that secrets remain protected.

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

For provider plugins, focus on verifying dynamic model resolution and ensuring the catalog behaves correctly when API keys are present.

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

If your code uses createPluginRuntimeStore, you need to mock the runtime in your tests to simulate the environment.

import { createPluginRuntimeStore } from "openclaw/plugin-sdk/runtime-store";
import type { PluginRuntime } from "openclaw/plugin-sdk/runtime-store";
const store = createPluginRuntimeStore<PluginRuntime>("test runtime not set");
// In test setup
const mockRuntime = {
agent: {
resolveAgentDir: vi.fn().mockReturnValue("/tmp/agent"),
// ... other mocks
},
config: {
loadConfig: vi.fn(),
writeConfigFile: vi.fn(),
},
// ... other namespaces
} as unknown as PluginRuntime;
store.setRuntime(mockRuntime);
// After tests
store.clearRuntime();

It is better to use per-instance stubs rather than mutating prototypes. This keeps your tests isolated and prevents side effects.

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

Bundled plugins include contract tests. These verify that registration ownership and runtime compliance are correct.

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

These tests check:

  • Which plugins register which providers
  • Which plugins register which speech providers
  • Registration shape correctness
  • Runtime contract compliance

You can run tests for a specific plugin or focus only on contract tests.

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

For contract tests only:

Terminal window
pnpm test -- src/plugins/contracts/shape.contract.test.ts
pnpm test -- src/plugins/contracts/auth.contract.test.ts
pnpm test -- src/plugins/contracts/runtime.contract.test.ts

If you are working on in-repo plugins, pnpm check enforces three specific rules to keep the codebase clean:

  1. No monolithic root imports — You cannot use the openclaw/plugin-sdk root barrel.
  2. No direct src/ imports — Plugins are not allowed to import ../../src/ directly.
  3. No self-imports — Plugins cannot import their own plugin-sdk/<name> subpath.

While external plugins do not have to follow these rules, sticking to these patterns is a good idea.

OpenClaw uses Vitest. You can run tests for the whole project or target specific files and filters.

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

If you run into memory issues during local runs, use these environment variables:

Terminal window
OPENCLAW_TEST_PROFILE=low OPENCLAW_TEST_SERIAL_GATEWAY=1 pnpm test

Need more help? Try the AI Setup Assistant.

OpenClaw

OpenClaw Expert

Noch festgefahren?

Wenn diese Seite nicht hilft, frage OpenClaw Expert nach Schritt-fuer-Schritt-Loesungen.