Testing Custom Actions

Unit tests give custom Hexabot actions a stable contract before the action is published or reused across projects. A good action test suite should cover the workflow-facing contract, the execution beh

This guide uses Jest examples for reusable hexabot-action-* packages, but the same patterns apply to project-local actions compiled under dist/extensions/actions/**/*.action.js.

What to Test

Test each action at three levels:

  • Schema contract: valid and invalid input, settings, and output payloads.

  • Execution behavior: service calls, HTTP requests, SDK calls, memory updates, bindings, and normalized return values.

  • Failure behavior: missing credentials, validation failures, upstream errors, network errors, retry-sensitive side effects, and safe logging.

Use focused unit tests. Do not bootstrap the full Hexabot API unless the test is explicitly verifying NestJS module wiring.

Install Jest Dependencies

For a standalone TypeScript action package, install Jest with a TypeScript transform. This example uses @swc/jest, which matches the Hexabot monorepo test style.

npm install -D jest @swc/core @swc/jest @types/jest typescript

With pnpm:

pnpm add -D jest @swc/core @swc/jest @types/jest typescript

Add scripts to package.json:

{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:cov": "jest --coverage"
  }
}

Jest Configuration

Create jest.config.cjs:

If the package uses TypeScript path aliases, add moduleNameMapper entries or avoid aliases in testable action packages.

Test File Layout

Keep tests close to the action source:

Or keep all tests under test/:

Use the layout your package already uses, then make testRegex or testMatch match it.

Instantiate an Action

createAction() returns an injectable action class. For a unit test, instantiate it directly with a mocked ActionService.

When your action extends BaseAction and injects additional services, pass mocks for those constructor arguments:

Test the Schema Contract

Use parseInput, parseSettings, and parseOutput to test the contract that workflow authors rely on.

Schema tests should cover:

  • required fields;

  • invalid formats such as email, URL, UUID, and enum values;

  • cross-field validation rules;

  • default settings;

  • the success output shape;

  • the failure output shape;

  • rejection of unsupported extra fields when schemas are strict.

Do not redefine timeout_ms or retries in an action settings schema. Hexabot parses those base settings for every action.

Test Execution with Mocked Context Services

Actions receive runtime dependencies through context.services, context.event, context.memoryStore, and bindings. Unit tests can pass the minimum context shape the action needs.

Call execute() when you want to isolate action logic. Call run() when you want the base runtime to validate input, validate output, apply timeout/retry settings, and reject unsupported bindings.

Mock HTTP Calls

Prefer mocking the HTTP client instead of calling external APIs. If the action uses fetch, spy on global.fetch.

If the action uses axios, mock the module:

Test Failure Paths

Failure tests are as important as success tests because workflows often branch on failure outputs.

Also test malformed upstream payloads, network errors, missing required context, and any idempotency behavior for write actions.

Test Memory Actions

For memory actions, mock context.memoryStore.

Validate memory slugs and make sure the output is JSON-serializable.

Test Conversational Actions

Conversational actions usually need context.event. Mock only the methods the action calls.

Test the missing-event path when the action cannot run outside a conversational workflow.

Restrict conversational-only actions with workflowTypes in the action metadata, then test that workflow-specific assumptions are enforced by the action logic.

Test Bindings

When an action consumes runtime bindings, test both the direct execution path and the base runtime validation path.

For custom binding kinds, instantiate the binding provider with a mocked RuntimeBindingsService and assert registration:

Test Safe Logging

Log assertions should prove that useful metadata is logged without leaking secrets.

Avoid snapshots for logs or upstream payloads that may contain credentials or PII.

Run Tests Before Publishing

Run the package checks before publishing or installing the action in a shared project:

With pnpm:

For actions inside the Hexabot monorepo, run the API package tests:

Use a clean Hexabot project for final installation verification, but keep that as a package integration check. Unit tests should stay fast and deterministic.

Checklist

  • Valid input parses successfully.

  • Invalid input fails with useful schema errors.

  • Settings parse with action settings and base timeout_ms/retries.

  • Success and failure outputs match outputSchema.

  • External clients are mocked.

  • Credentials are resolved through context.services.credentials.

  • Missing or empty credential values are tested.

  • Non-2xx, network, and malformed upstream responses are tested.

  • Logs do not include raw secrets or full sensitive payloads.

  • Memory and conversational context assumptions are tested when relevant.

  • Binding support is tested when the action consumes bindings.

  • action.run() is covered when timeout, retry, schema, or binding behavior is part of the contract.

Last updated

Was this helpful?