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

ExportPurpose
createTestPluginApiConstruct a minimal mock of the plugin API for unit tests that register directly. Access it via plugin-sdk/plugin-test-api
AUTH_PROFILE_RUNTIME_CONTRACTA 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_CONTRACTA shared fixture for delivery-suppression contracts used by native agent runtime adapters. Obtain it from plugin-sdk/agent-runtime-test-contracts
OUTCOME_FALLBACK_RUNTIME_CONTRACTA shared fixture for fallback-classification contracts used by native agent runtime adapters. Obtain it from plugin-sdk/agent-runtime-test-contracts
createParameterFreeToolCreate dynamic-tool schema fixtures for native runtime contract tests. Source them from plugin-sdk/agent-runtime-test-contracts
expectChannelInboundContextContractVerify the shape of inbound channel contexts. Import it from plugin-sdk/channel-contract-testing
installChannelOutboundPayloadContractSuiteSet up contract cases for outbound channel payloads. Import them from plugin-sdk/channel-contract-testing
createStartAccountContextBuild lifecycle contexts for channel accounts. Import them from plugin-sdk/channel-test-helpers
installChannelActionsContractSuiteSet up generic contract cases for channel message actions. Import them from plugin-sdk/channel-test-helpers
installChannelSetupContractSuiteSet up generic contract cases for channel setup. Import them from plugin-sdk/channel-test-helpers
installChannelStatusContractSuiteSet up generic contract cases for channel status. Import them from plugin-sdk/channel-test-helpers
expectDirectoryIdsValidate directory IDs returned by a channel's directory-listing function. Import it from plugin-sdk/channel-test-helpers
assertBundledChannelEntriesConfirm that bundled channel entrypoints expose the expected public contract. Import it from plugin-sdk/channel-test-helpers
formatEnvelopeTimestampNormalize deterministic envelope timestamps. Import it from plugin-sdk/channel-test-helpers
expectPairingReplyTextValidate the reply text from a channel pairing and extract its code. Import it from plugin-sdk/channel-test-helpers
describePluginRegistrationContractSet up contract checks for plugin registration. Import them from plugin-sdk/plugin-test-contracts
registerSingleProviderPluginRegister a single provider plugin in loader smoke tests. Import it from plugin-sdk/plugin-test-runtime
registerProviderPluginRetrieve all provider types from a single plugin. Import it from plugin-sdk/plugin-test-runtime
registerProviderPluginsRetrieve provider registrations across multiple plugins. Import them from plugin-sdk/plugin-test-runtime
requireRegisteredProviderCheck that a provider collection includes a specific ID. Import it from plugin-sdk/plugin-test-runtime
createRuntimeEnvCreate a mocked runtime environment for CLI or plugin testing. Import it from plugin-sdk/plugin-test-runtime
createPluginRuntimeMockCreate a mocked surface for plugin runtime interactions. Import it from plugin-sdk/plugin-test-runtime
createPluginSetupWizardStatusBuild helpers for channel plugin setup status. Import them from plugin-sdk/plugin-test-runtime
createTestWizardPrompterCreate a mocked prompter for setup wizards. Import it from plugin-sdk/plugin-test-runtime
createRuntimeTaskFlowGenerate isolated state for runtime task flows. Import it from plugin-sdk/plugin-test-runtime
runProviderCatalogRun a provider catalog hook with test dependencies in place. Import it from plugin-sdk/plugin-test-runtime
resolveProviderWizardOptionsDetermine provider setup wizard selections in contract tests. Import it from plugin-sdk/plugin-test-runtime
resolveProviderModelPickerEntriesDetermine provider model-picker entries in contract tests. Import them from plugin-sdk/plugin-test-runtime
buildProviderPluginMethodChoiceBuild IDs for provider wizard choices used in assertions. Import them from plugin-sdk/plugin-test-runtime
setProviderWizardProvidersResolverForTestInject provider wizard providers for isolated test scenarios. Import them from plugin-sdk/plugin-test-runtime
describeOpenAIProviderRuntimeContractVerify runtime contract checks for provider families. Import from plugin-sdk/provider-test-contracts
expectPassthroughReplayPolicyConfirm provider replay policies pass through tools and metadata owned by the provider. Import from plugin-sdk/provider-test-contracts
runRealtimeSttLiveTestExecute a live realtime STT provider test using shared audio fixtures. Import from plugin-sdk/provider-test-contracts
normalizeTranscriptForMatchStandardize live transcript output before applying fuzzy assertions. Import from plugin-sdk/provider-test-contracts
expectExplicitVideoGenerationCapabilitiesEnsure video providers specify explicit generation mode capabilities. Import from plugin-sdk/provider-test-contracts
expectExplicitMusicGenerationCapabilitiesEnsure music providers declare explicit generation and edit capabilities. Import from plugin-sdk/provider-test-contracts
mockSuccessfulDashscopeVideoTaskSet up a successful DashScope-compatible video task response. Import from plugin-sdk/provider-test-contracts
getProviderHttpMocksAccess opt-in HTTP and auth mocks for providers via Vitest. Import from plugin-sdk/provider-http-test-mocks
installProviderHttpMockCleanupClear provider HTTP and auth mocks after every test. Import from plugin-sdk/provider-http-test-mocks
installCommonResolveTargetErrorCasesShared test scenarios for handling target resolution errors. Import from plugin-sdk/channel-target-testing
shouldAckReactionDetermine if a channel should add an ack reaction. Import from plugin-sdk/channel-feedback
removeAckReactionAfterReplyRemove the ack reaction once the reply is delivered. Import from plugin-sdk/channel-feedback
createTestRegistryCreate a channel plugin registry fixture. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
createEmptyPluginRegistryCreate an empty plugin registry fixture. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
setActivePluginRegistrySet up a registry fixture for plugin runtime tests. Import from plugin-sdk/plugin-test-runtime or plugin-sdk/channel-test-helpers
createRequestCaptureJsonFetchRecord JSON fetch requests during media helper tests. Import from plugin-sdk/test-media-understanding
isLiveTestEnabledGuard opt-in live provider tests behind a gate. Import from plugin-sdk/test-live
collectProviderApiKeysFind credentials for live provider tests. Import from plugin-sdk/test-live-auth
parseProviderModelMapRead model overrides for music and video live tests. Import from plugin-sdk/test-media-generation
withServerRun tests against a temporary local HTTP server. Import from plugin-sdk/test-env
createMockIncomingRequestConstruct a minimal incoming HTTP request object. Import from plugin-sdk/test-env
withFetchPreconnectRun fetch tests with preconnect hooks in place. Import from plugin-sdk/test-env
withEnv / withEnvAsyncTemporarily patch environment variables. Import from plugin-sdk/test-env
createTempHomeEnv / withTempHome / withTempDirCreate isolated filesystem test fixtures. Import from plugin-sdk/test-env
createMockServerResponseBuild a minimal HTTP server response mock. Import from plugin-sdk/test-env
createProviderUsageFetchConstruct provider usage fetch fixtures. Import from plugin-sdk/test-env
useFrozenTime / useRealTimeManage timer freezing and restoration for tests that depend on time. Import from plugin-sdk/test-env
createCliRuntimeCaptureCapture CLI output during test execution. Import from plugin-sdk/test-fixtures
importFreshModuleLoad an ESM module using a fresh query token to avoid the module cache. Import from plugin-sdk/test-fixtures
bundledPluginRoot / bundledPluginFileLook up bundled plugin source or dist fixture paths. Import from plugin-sdk/test-fixtures
mockNodeBuiltinModuleInstall minimal Vitest mocks for Node builtins. Import from plugin-sdk/test-node-mocks
createSandboxTestContextConstruct sandbox test environments. Import from plugin-sdk/test-fixtures
writeSkillCreate skill test fixtures. Import from plugin-sdk/test-fixtures
makeAgentAssistantMessageBuild fixtures for agent transcript messages. Import from plugin-sdk/test-fixtures
peekSystemEvents / resetSystemEventsForTestExamine and reset system event fixtures. Import from plugin-sdk/test-fixtures
sanitizeTerminalTextClean up terminal output before assertions. Import from plugin-sdk/test-fixtures
countLines / hasBalancedFencesValidate chunking output structure. Import from plugin-sdk/test-fixtures
typedCasesKeep 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:

CommandEnforces
pnpm run lint:plugins:no-monolithic-plugin-sdk-entry-importsA bundled plugin is prohibited from importing the monolithic openclaw/plugin-sdk root barrel.
pnpm run lint:plugins:no-extension-src-importsProduction extension files must not import the repo src/** tree directly (../../src/...).
pnpm run lint:plugins:no-extension-test-core-importsExtension 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