OpenClaw Plugin Testing Utilities and Patterns

Reference for testing OpenClaw plugins, covering test utilities, patterns, and lint enforcement. Essential for plugin developers writing or maintaining tests.

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 enforcement for OpenClaw plugins.

Tip

Need test examples? The how-to guides provide worked examples: Channel plugin tests and Provider plugin tests.

Test utilities

These subpaths act as repo-local source entrypoints for OpenClaw's bundled plugin tests. They are not exposed as package.json exports for third-party plugins, and they may rely on Vitest or other repo-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 { 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";

For bundled plugin tests, stick to these focused subpaths. The earlier openclaw/plugin-sdk/testing barrel was repo-local, omitted from shipped packages, and has since been removed. The previous openclaw/plugin-sdk/test-utils alias was also dropped at that time. pnpm run lint:plugins:no-extension-test-core-imports (scripts/check-no-extension-test-core-imports.ts) keeps extension tests on the focused test subpaths listed above.

Available exports

ExportPurpose
createTestPluginApiConstruct a minimal mock of the plugin API for direct registration unit tests. Pull it in via plugin-sdk/plugin-test-api
AUTH_PROFILE_RUNTIME_CONTRACTReusable auth-profile contract fixture meant for native agent runtime adapters. Source it from plugin-sdk/agent-runtime-test-contracts
DELIVERY_NO_REPLY_RUNTIME_CONTRACTReusable delivery suppression contract fixture meant for native agent runtime adapters. Source it from plugin-sdk/agent-runtime-test-contracts
OUTCOME_FALLBACK_RUNTIME_CONTRACTReusable fallback-classification contract fixture meant for native agent runtime adapters. Source it from plugin-sdk/agent-runtime-test-contracts
createParameterFreeToolGenerate dynamic-tool schema fixtures for native runtime contract tests. Source it from plugin-sdk/agent-runtime-test-contracts
expectChannelInboundContextContractVerify the shape of channel inbound context. Source it from plugin-sdk/channel-contract-testing
installChannelOutboundPayloadContractSuiteLoad contract cases for channel outbound payloads. Source it from plugin-sdk/channel-contract-testing
createStartAccountContextCreate lifecycle contexts for channel accounts. Source it from plugin-sdk/channel-test-helpers
installChannelActionsContractSuiteLoad generic contract cases for channel message actions. Source it from plugin-sdk/channel-test-helpers
installChannelSetupContractSuiteLoad generic contract cases for channel setup. Source it from plugin-sdk/channel-test-helpers
installChannelStatusContractSuiteLoad generic contract cases for channel status. Source it from plugin-sdk/channel-test-helpers
expectDirectoryIdsVerify channel directory ids from a directory-list function. Source it from plugin-sdk/channel-test-helpers
formatEnvelopeTimestampProduce deterministic envelope timestamps. Source it from plugin-sdk/channel-test-helpers
expectPairingReplyTextVerify channel pairing reply text and pull out its code. Source it from plugin-sdk/channel-test-helpers
describePluginRegistrationContractLoad checks for plugin registration contracts. Source it from plugin-sdk/plugin-test-contracts
registerSingleProviderPluginRegister a single provider plugin in loader smoke tests. Source it from plugin-sdk/plugin-test-runtime
registerProviderPluginCollect every provider kind from one plugin. Source it from plugin-sdk/plugin-test-runtime
registerProviderPluginsCollect provider registrations from multiple plugins. Source it from plugin-sdk/plugin-test-runtime
requireRegisteredProviderConfirm a provider collection holds a given id. Source it from plugin-sdk/plugin-test-runtime
createRuntimeEnvConstruct a mocked CLI and plugin runtime environment. Source it from plugin-sdk/plugin-test-runtime
createPluginRuntimeMockConstruct a mocked plugin runtime surface. Source it from plugin-sdk/plugin-test-runtime
createPluginSetupWizardStatusBuild setup status helpers for channel plugins. Source it from plugin-sdk/plugin-test-runtime
createTestWizardPrompterConstruct a mocked setup wizard prompter. Source it from plugin-sdk/plugin-test-runtime
createRuntimeTaskFlowSet up isolated runtime task-flow state. Source it from plugin-sdk/plugin-test-runtime
runProviderCatalogRun a provider catalog hook with test dependencies. Source it from plugin-sdk/plugin-test-runtime
resolveProviderWizardOptionsDetermine provider setup wizard choices in contract tests. Source it from plugin-sdk/plugin-test-runtime
resolveProviderModelPickerEntriesDetermine provider model-picker entries in contract tests. Source it from plugin-sdk/plugin-test-runtime
buildProviderPluginMethodChoiceGenerate provider wizard choice ids for assertions. Source it from plugin-sdk/plugin-test-runtime
setProviderWizardProvidersResolverForTestSupply provider wizard providers for isolated tests. Source it from plugin-sdk/plugin-test-runtime
describeOpenAIProviderRuntimeContractLoad runtime contract checks for provider families. Source it from plugin-sdk/provider-test-contracts
expectPassthroughReplayPolicyVerify that provider replay policies forward provider-owned tools and metadata. Pull from plugin-sdk/provider-test-contracts
runRealtimeSttLiveTestExecute a live realtime STT provider test using shared audio fixtures. Pull from plugin-sdk/provider-test-contracts
normalizeTranscriptForMatchStandardize live transcript output ahead of fuzzy assertions. Pull from plugin-sdk/provider-test-contracts
expectExplicitVideoGenerationCapabilitiesConfirm video providers advertise explicit generation mode capabilities. Pull from plugin-sdk/provider-test-contracts
expectExplicitMusicGenerationCapabilitiesConfirm music providers advertise explicit generation and edit capabilities. Pull from plugin-sdk/provider-test-contracts
mockSuccessfulDashscopeVideoTaskSet up a working DashScope-compatible video task response. Pull from plugin-sdk/provider-test-contracts
getProviderHttpMocksReach opt-in provider HTTP and auth Vitest mocks. Pull from plugin-sdk/provider-http-test-mocks
installProviderHttpMockCleanupClear provider HTTP and auth mocks after every test. Pull from plugin-sdk/provider-http-test-mocks
createOpenClawTestState / withOpenClawTestState / OpenClawTestStateSet up and tear down isolated OpenClaw state, config, workspace, environment, and auth-profile fixtures. Pull from plugin-sdk/test-state
installCommonResolveTargetErrorCasesReusable test cases for target resolution error handling. Pull from plugin-sdk/channel-target-testing
shouldAckReactionDetermine if a channel should add an ack reaction. Pull from plugin-sdk/channel-feedback
removeAckReactionAfterReplyTake away the ack reaction once the reply is delivered. Pull from plugin-sdk/channel-feedback
createTestRegistryConstruct a channel plugin registry fixture. Pull from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
createEmptyPluginRegistryConstruct an empty plugin registry fixture. Pull from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
setActivePluginRegistrySet up a registry fixture for plugin runtime tests. Pull from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
createRequestCaptureJsonFetchRecord JSON fetch requests during media helper tests. Pull from plugin-sdk/test-media-understanding
isLiveTestEnabledRestrict opt-in live provider tests. Pull from plugin-sdk/test-live
collectProviderApiKeysLocate credentials for live provider tests. Pull from plugin-sdk/test-live-auth
parseProviderModelMapRead music and video live-test model overrides. Pull from plugin-sdk/test-media-generation
withServerRun tests against a temporary local HTTP server. Pull from plugin-sdk/test-env
createMockIncomingRequestCreate a minimal incoming HTTP request object. Pull from plugin-sdk/test-env
withFetchPreconnectExecute fetch tests with preconnect hooks in place. Pull from plugin-sdk/test-env
withEnv / withEnvAsyncTemporarily modify environment variables. Pull from plugin-sdk/test-env
createTempHomeEnv / withTempHome / withTempDirGenerate isolated filesystem test fixtures. Pull from plugin-sdk/test-env
createMockServerResponseGenerate a minimal HTTP server response mock. Pull from plugin-sdk/test-env
createProviderUsageFetchConstruct provider usage fetch fixtures. Pull from plugin-sdk/test-env
useFrozenTime / useRealTimePause and resume timers when tests depend on time. Pull them in from plugin-sdk/test-env
createCliRuntimeCaptureGrab CLI output produced at runtime for test assertions. Source it from plugin-sdk/test-fixtures
runDirectImportSmokeExecute a plugin's public-surface import inside a separate Node process. Get it from plugin-sdk/test-fixtures
importFreshModuleBring in an ESM module using a new query token so the module cache is skipped. Obtain it from plugin-sdk/test-fixtures
bundledPluginRoot / bundledPluginFileLocate paths for bundled plugin source or dist fixtures. Fetch them from plugin-sdk/test-fixtures
mockNodeBuiltinModuleSet up minimal Node builtin mocks for Vitest. Take them from plugin-sdk/test-node-mocks
createSandboxTestContextCreate sandboxed test contexts. Derive them from plugin-sdk/test-fixtures
writeSkillGenerate skill fixture data. Pull from plugin-sdk/test-fixtures
makeAgentAssistantMessageAssemble agent transcript message fixtures. Get them from plugin-sdk/test-fixtures
peekSystemEvents / resetSystemEventsForTestReview and clear system event fixtures. Source from plugin-sdk/test-fixtures
sanitizeTerminalTextClean terminal output so assertions can compare it. Import from plugin-sdk/test-fixtures
countLines / hasBalancedFencesVerify the structure of chunking results. Import from plugin-sdk/test-fixtures
typedCasesKeep literal types intact for table-driven tests. Import from plugin-sdk/test-fixtures

Contract suites for bundled plugins also rely on these SDK testing subpaths for test-only registry, manifest, public-artifact, and runtime fixture helpers. Suites that are core-only and depend on bundled OpenClaw inventory remain under src/plugins/contracts.

Types

Testing subpaths that are more focused also re-export types that come in handy within 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

Add standard error cases for channel target resolution with 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", () => {
    // ...
  });
});

Testing patterns

Testing registration contracts

When unit tests hand a manually written api mock to register(api), they skip OpenClaw's loader acceptance checks. For every registration surface your plugin relies on, add at least one smoke test backed by the loader, particularly for hooks and exclusive capabilities like memory.

If required metadata is absent, or a plugin invokes a capability API it does not own, the real loader rejects registration. As an example, api.registerHook(...) needs a hook name, while api.registerMemoryCapability(...) expects the plugin manifest or exported entry to declare kind: "memory".

Testing runtime config access

Go with the shared plugin runtime mock available from openclaw/plugin-sdk/plugin-test-runtime. Its runtime config helpers reflect 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 relies on createPluginRuntimeStore, mock the runtime during 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

Use per-instance stubs rather than altering the prototype:

// 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)

Contract tests for bundled plugins confirm registration ownership:

pnpm test src/plugins/contracts/

These tests verify:

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

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.mts executes a collection of lint:plugins:* import-boundary validations during CI, though each one can also be invoked independently on a local machine:

CommandEnforces
pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-importsPlugins that ship bundled must not pull in the monolithic openclaw/plugin-sdk root barrel.
pnpm run lint:plugins:no-extension-src-importsFiles for production extensions are barred from importing the repo src/** tree directly (../../src/...).
pnpm run lint:plugins:no-extension-test-core-importsTest files for extensions cannot rely on removed SDK test aliases or other helpers exclusive to the core.

These lint rules do not apply to external plugins, yet adopting the same conventions is advised.

Test configuration

Vitest 4, paired with informational V8 coverage reporting, is the testing setup OpenClaw relies on. For tests written against 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

Should memory pressure appear during local execution:

OPENCLAW_VITEST_MAX_WORKERS=1 pnpm test
2,163 words · updated Aug 17, 2026