Testing Locally

Plugin testing does not require a running VeloCMS instance. The @velocms/plugin-sdk/test-helpers package exports a mockVelocms factory that returns an in-memory context object with the same shape as the real PluginContext. You can pre-seed settings, inspect log calls, intercept fetch requests, and simulate KV store operations.

The mockVelocms factory

import { mockVelocms } from "@velocms/plugin-sdk/test-helpers";

const ctx = mockVelocms({
  tenant: { id: "t1", slug: "test-blog", locale: "en-US" },
  settings: { api_key: "test-key-us1", list_id: "abc123" },
  fetchResponse: { ok: true, status: 200, data: { id: "member_id" } },
  kvStore: { existing_key: "existing_value" },
});

Asserting on log output

ctx.log.info("something happened", { detail: "data" });

// ctx._logs records every log call
assert.equal(ctx._logs.length, 1);
assert.equal(ctx._logs[0]?.level, "info");
assert.equal(ctx._logs[0]?.message, "something happened");

Intercepting fetch calls

const ctx = mockVelocms({
  fetchResponse: { ok: true, status: 200, data: { success: true } },
});

await myHandler(payload, ctx as never);

// ctx._fetchCalls records every fetch invocation
assert.equal(ctx._fetchCalls.length, 1);
assert.ok(ctx._fetchCalls[0]?.url.includes("api.example.com"));
assert.equal(ctx._fetchCalls[0]?.options?.method, "POST");

Testing handler return values

For hooks like beforePostPublish where the return value modifies the publish action, assert that your handler mutates the payload correctly:

import { beforePostPublish } from "../src/index.js";

test("adds auto-tags from AI analysis", async () => {
  const ctx = mockVelocms({ settings: { openai_key: "sk-test" } });
  const payload = { post: { title: "Sourdough baking tips", tags: [], /* ... */ } };

  const result = await beforePostPublish(payload as never, ctx as never);

  assert.ok(Array.isArray(result.suggestedTags));
  assert.ok(result.suggestedTags!.length > 0);
});

Running tests

# Using velocms CLI (recommended)
velocms test

# Or directly with Vitest
npx vitest run

# Watch mode during development
velocms test --watch

Integration testing against a local VeloCMS

Unit tests cover the handler logic in isolation. For end-to-end testing — verifying that the hook actually fires when a post is published on a real tenant — you need a local VeloCMS dev server. Set VELOCMS_DEV_URL=http://localhost:3000 and run velocms dev from your plugin directory. VeloCMS hot-reloads your built dist/ on each compile cycle.