Plugin testing utilities and patterns for OpenClaw plugins
Reference material covering test utilities, patterns, and lint rules for OpenClaw plugins. Includes available exports and guidance for writing tests for bundled plugins.
Read this when
- You are writing tests for a plugin
- You need test utilities from the plugin SDK
- You want to understand contract tests for bundled plugins
Reference material covering test utilities, patterns, and lint rules for OpenClaw plugins.
Tip
Need test examples? The how-to guides contain worked examples for testing: Channel plugin tests and Provider plugin tests.
Test utilities
These subpaths serve as local source entry points for OpenClaw's own bundled plugin tests. They are not made available as package.json exports intended for third-party plugins, and they may rely on Vitest or other repository-only test dependencies.
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";
Apply these targeted subpaths when writing tests for bundled plugins. The previous openclaw/plugin-sdk/testing barrel was local to the repository, not included in published packages, and has since been removed. The earlier openclaw/plugin-sdk/test-utils alias was eliminated at the same time. pnpm run lint:plugins:no-extension-test-core-imports (scripts/check-no-extension-test-core-imports.ts) keeps extension tests directed to the focused test subpaths listed above.
Available exports
| Export | Purpose |
|---|---|
createTestPluginApi | Construct a minimal mock of the plugin API for unit tests that register directly. Access it via plugin-sdk/plugin-test-api |
AUTH_PROFILE_RUNTIME_CONTRACT | A shared fixture for auth-profile contracts used by native agent runtime adapters. Obtain it from plugin-sdk/agent-runtime-test-contracts |
DELIVERY_NO_REPLY_RUNTIME_CONTRACT | A shared fixture for delivery-suppression contracts used by native agent runtime adapters. Obtain it from plugin-sdk/agent-runtime-test-contracts |
OUTCOME_FALLBACK_RUNTIME_CONTRACT | A shared fixture for fallback-classification contracts used by native agent runtime adapters. Obtain it from plugin-sdk/agent-runtime-test-contracts |
createParameterFreeTool | Create dynamic-tool schema fixtures for native runtime contract tests. Source them from plugin-sdk/agent-runtime-test-contracts |
expectChannelInboundContextContract | Verify the shape of inbound channel contexts. Import it from plugin-sdk/channel-contract-testing |
installChannelOutboundPayloadContractSuite | Set up contract cases for outbound channel payloads. Import them from plugin-sdk/channel-contract-testing |
createStartAccountContext | Build lifecycle contexts for channel accounts. Import them from plugin-sdk/channel-test-helpers |
installChannelActionsContractSuite | Set up generic contract cases for channel message actions. Import them from plugin-sdk/channel-test-helpers |
installChannelSetupContractSuite | Set up generic contract cases for channel setup. Import them from plugin-sdk/channel-test-helpers |
installChannelStatusContractSuite | Set up generic contract cases for channel status. Import them from plugin-sdk/channel-test-helpers |
expectDirectoryIds | Validate directory IDs returned by a channel's directory-listing function. Import it from plugin-sdk/channel-test-helpers |
assertBundledChannelEntries | Confirm that bundled channel entrypoints expose the expected public contract. Import it from plugin-sdk/channel-test-helpers |
formatEnvelopeTimestamp | Normalize deterministic envelope timestamps. Import it from plugin-sdk/channel-test-helpers |
expectPairingReplyText | Validate the reply text from a channel pairing and extract its code. Import it from plugin-sdk/channel-test-helpers |
describePluginRegistrationContract | Set up contract checks for plugin registration. Import them from plugin-sdk/plugin-test-contracts |
registerSingleProviderPlugin | Register a single provider plugin in loader smoke tests. Import it from plugin-sdk/plugin-test-runtime |
registerProviderPlugin | Retrieve all provider types from a single plugin. Import it from plugin-sdk/plugin-test-runtime |
registerProviderPlugins | Retrieve provider registrations across multiple plugins. Import them from plugin-sdk/plugin-test-runtime |
requireRegisteredProvider | Check that a provider collection includes a specific ID. Import it from plugin-sdk/plugin-test-runtime |
createRuntimeEnv | Create a mocked runtime environment for CLI or plugin testing. Import it from plugin-sdk/plugin-test-runtime |
createPluginRuntimeMock | Create a mocked surface for plugin runtime interactions. Import it from plugin-sdk/plugin-test-runtime |
createPluginSetupWizardStatus | Build helpers for channel plugin setup status. Import them from plugin-sdk/plugin-test-runtime |
createTestWizardPrompter | Create a mocked prompter for setup wizards. Import it from plugin-sdk/plugin-test-runtime |
createRuntimeTaskFlow | Generate isolated state for runtime task flows. Import it from plugin-sdk/plugin-test-runtime |
runProviderCatalog | Run a provider catalog hook with test dependencies in place. Import it from plugin-sdk/plugin-test-runtime |
resolveProviderWizardOptions | Determine provider setup wizard selections in contract tests. Import it from plugin-sdk/plugin-test-runtime |
resolveProviderModelPickerEntries | Determine provider model-picker entries in contract tests. Import them from plugin-sdk/plugin-test-runtime |
buildProviderPluginMethodChoice | Build IDs for provider wizard choices used in assertions. Import them from plugin-sdk/plugin-test-runtime |
setProviderWizardProvidersResolverForTest | Inject provider wizard providers for isolated test scenarios. Import them from plugin-sdk/plugin-test-runtime |
describeOpenAIProviderRuntimeContract | Verify runtime contract checks for provider families. Import from plugin-sdk/provider-test-contracts |
expectPassthroughReplayPolicy | Confirm provider replay policies pass through tools and metadata owned by the provider. Import from plugin-sdk/provider-test-contracts |
runRealtimeSttLiveTest | Execute a live realtime STT provider test using shared audio fixtures. Import from plugin-sdk/provider-test-contracts |
normalizeTranscriptForMatch | Standardize live transcript output before applying fuzzy assertions. Import from plugin-sdk/provider-test-contracts |
expectExplicitVideoGenerationCapabilities | Ensure video providers specify explicit generation mode capabilities. Import from plugin-sdk/provider-test-contracts |
expectExplicitMusicGenerationCapabilities | Ensure music providers declare explicit generation and edit capabilities. Import from plugin-sdk/provider-test-contracts |
mockSuccessfulDashscopeVideoTask | Set up a successful DashScope-compatible video task response. Import from plugin-sdk/provider-test-contracts |
getProviderHttpMocks | Access opt-in HTTP and auth mocks for providers via Vitest. Import from plugin-sdk/provider-http-test-mocks |
installProviderHttpMockCleanup | Clear provider HTTP and auth mocks after every test. Import from plugin-sdk/provider-http-test-mocks |
installCommonResolveTargetErrorCases | Shared test scenarios for handling target resolution errors. Import from plugin-sdk/channel-target-testing |
shouldAckReaction | Determine if a channel should add an ack reaction. Import from plugin-sdk/channel-feedback |
removeAckReactionAfterReply | Remove the ack reaction once the reply is delivered. Import from plugin-sdk/channel-feedback |
createTestRegistry | Create a channel plugin registry fixture. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers |
createEmptyPluginRegistry | Create an empty plugin registry fixture. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers |
setActivePluginRegistry | Set up a registry fixture for plugin runtime tests. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers |
createRequestCaptureJsonFetch | Record JSON fetch requests during media helper tests. Import from plugin-sdk/test-media-understanding |
isLiveTestEnabled | Guard opt-in live provider tests behind a gate. Import from plugin-sdk/test-live |
collectProviderApiKeys | Find credentials for live provider tests. Import from plugin-sdk/test-live-auth |
parseProviderModelMap | Read model overrides for music and video live tests. Import from plugin-sdk/test-media-generation |
withServer | Run tests against a temporary local HTTP server. Import from plugin-sdk/test-env |
createMockIncomingRequest | Construct a minimal incoming HTTP request object. Import from plugin-sdk/test-env |
withFetchPreconnect | Run fetch tests with preconnect hooks in place. Import from plugin-sdk/test-env |
withEnv / withEnvAsync | Temporarily patch environment variables. Import from plugin-sdk/test-env |
createTempHomeEnv / withTempHome / withTempDir | Create isolated filesystem test fixtures. Import from plugin-sdk/test-env |
createMockServerResponse | Build a minimal HTTP server response mock. Import from plugin-sdk/test-env |
createProviderUsageFetch | Construct provider usage fetch fixtures. Import from plugin-sdk/test-env |
useFrozenTime / useRealTime | Manage timer freezing and restoration for tests that depend on time. Import from plugin-sdk/test-env |
createCliRuntimeCapture | Capture CLI output during test execution. Import from plugin-sdk/test-fixtures |
importFreshModule | Load an ESM module using a fresh query token to avoid the module cache. Import from plugin-sdk/test-fixtures |
bundledPluginRoot / bundledPluginFile | Look up bundled plugin source or dist fixture paths. Import from plugin-sdk/test-fixtures |
mockNodeBuiltinModule | Install minimal Vitest mocks for Node builtins. Import from plugin-sdk/test-node-mocks |
createSandboxTestContext | Construct sandbox test environments. Import from plugin-sdk/test-fixtures |
writeSkill | Create skill test fixtures. Import from plugin-sdk/test-fixtures |
makeAgentAssistantMessage | Build fixtures for agent transcript messages. Import from plugin-sdk/test-fixtures |
peekSystemEvents / resetSystemEventsForTest | Examine and reset system event fixtures. Import from plugin-sdk/test-fixtures |
sanitizeTerminalText | Clean up terminal output before assertions. Import from plugin-sdk/test-fixtures |
countLines / hasBalancedFences | Validate chunking output structure. Import from plugin-sdk/test-fixtures |
typedCases | Keep literal types intact for table-driven tests. Import from plugin-sdk/test-fixtures |
The SDK testing subpaths are also used by bundled plugin contract suites for
test only registry, manifest, public artifact, and runtime fixture helpers.
Core only suites depending on bundled OpenClaw inventory stay under
src/plugins/contracts instead.
Types
Testing subpaths additionally re export types that are helpful in test files:
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";
Testing target resolution
Use installCommonResolveTargetErrorCases to include standard error scenarios for
channel target resolution:
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", () => {
// ...
});
});
Testing patterns
Testing registration contracts
When unit tests pass a hand written api mock to register(api), they do not
exercise OpenClaw's loader acceptance gates. Include at least one loader backed
smoke test for each registration surface your plugin depends on, especially
hooks and exclusive capabilities such as memory.
The real loader rejects plugin registration when required metadata is absent or
a plugin calls a capability API it does not own. For instance,
api.registerHook(...) needs a hook name, and
api.registerMemoryCapability(...) requires the plugin manifest or exported
entry to declare kind: "memory".
Testing runtime config access
Use the shared plugin runtime mock from
openclaw/plugin-sdk/plugin-test-runtime when possible. Its runtime config helpers model the
current snapshot and mutation APIs.
Unit testing a channel plugin
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");
});
});
Unit testing a provider plugin
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);
});
});
Mocking the plugin runtime
For code that uses createPluginRuntimeStore, mock the runtime in tests:
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();
Testing with per-instance stubs
Prefer per instance stubs over prototype mutation:
// Preferred: per-instance stub
const client = new MyChannelClient();
client.sendMessage = vi.fn().mockResolvedValue({ id: "msg-1" });
// Avoid: prototype mutation
// MyChannelClient.prototype.sendMessage = vi.fn();
Contract tests (in-repo plugins)
Bundled plugins include contract tests that check registration ownership:
pnpm test src/plugins/contracts/
These tests verify:
- Which plugins register which providers
- Which plugins register which speech providers
- Correctness of registration shape
- Compliance with runtime contract
Running scoped tests
For a specific plugin:
pnpm test <bundled-plugin-root>/my-channel/
For contract tests only:
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 enforcement (in-repo plugins)
scripts/run-additional-boundary-checks.mjs runs a set of lint:plugins:*
import boundary checks in CI; each can also be executed standalone locally:
| Command | Enforces |
|---|---|
pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-imports | A bundled plugin is prohibited from importing the monolithic openclaw/plugin-sdk root barrel. |
pnpm run lint:plugins:no-extension-src-imports | Production extension files must not import the repo src/** tree directly (../../src/...). |
pnpm run lint:plugins:no-extension-test-core-imports | Extension test files are forbidden from importing removed SDK test aliases or other core-only test helpers. |
These lint rules do not apply to external plugins, though it is advisable to follow the same conventions.
Test configuration
OpenClaw uses Vitest 4 along with informational V8 coverage reporting. For testing plugins:
# 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
When memory pressure occurs during local runs:
OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test
Related
- SDK Overview -- import conventions
- SDK Channel Plugins -- channel plugin interface
- SDK Provider Plugins -- provider plugin hooks
- Building Plugins -- getting started guide