@desplega.ai/slack-mock

中文文档
0.6.0发布于 2026/11/08MITTypeScriptES Module零依赖构建来源已验证近 30 天下载 3.5万

Mock Slack server (Web API + Socket Mode) for end-to-end testing Slack bots

原始使用文档
文档目录10 节
  1. Install
  2. Quick start (from a checkout)
  3. UI
  4. In tests
  5. HTTP mode (signed requests)
  6. Frames from a journal
  7. Slack behaviour that is modelled
  8. Commands
  9. Hosted demo
  10. Docs

slack-mock

A mock Slack server for end-to-end testing Slack bots. It speaks the Slack Web API and Socket Mode, so a real @slack/bolt app connects to it unchanged (only clientOptions.slackApiUrl differs). Messages, threads, reactions, files, modals and assistant threads are stored, journaled as JSONL, and rendered as Slack-looking HTML for screenshots.

Built for the agent-swarm Slack integration. Bun + TypeScript, no runtime dependencies.

Install

bun add -d @desplega.ai/slack-mock          # from npm (once published)
bun add -d github:desplega-ai/slack-mock    # or straight from the repo (Bun runs the TypeScript sources)

Quick start (from a checkout)

bun install
bun run start                 # slack-mock serve --port 4040
bun run demo                  # same, seeded with threads, blocks, files, an ephemeral and a DM

Point your bot at it:

SLACK_BOT_TOKEN=xoxb-mock-bot-token \
SLACK_APP_TOKEN=xapp-mock-app-token \
SLACK_API_URL=http://127.0.0.1:4040/api/ \
bun src/http.ts               # agent-swarm reads SLACK_API_URL into clientOptions.slackApiUrl

Talk to the bot as a human and watch the thread:

curl -X POST http://127.0.0.1:4040/mock/messages -H 'content-type: application/json' \
  -d '{"channel":"general","user":"alice","text":"<@U0BOT00000> hello"}'
open http://127.0.0.1:4040/c/general            # live view: new messages arrive over SSE and flash (?live=0 turns it off, ?refresh=2 polls instead)
bun src/cli.ts screenshot http://127.0.0.1:4040/c/general --out general.png

UI

Use the avatar and name chip to choose who posts from every composer. The browser remembers your choice across channels and threads. Open /c/support?as=taras to select Taras. The page removes as from the URL after selection. Public workspaces show the chip after presenter sign-in. Optional user status text appears beside names in the users table and the chip's user list. Each message offers reaction and thread icons, including messages without replies. The thread icon opens the thread without focusing the composer. Icons stay visible on phones and touch screens. Desktop shows them on hover or keyboard focus. Choose an emoji from the picker beside the message, or select an existing reaction to add or remove your own. Reactions use the identity in the chip and require presenter sign-in in public workspaces. Below 700px, the sidebar becomes a top bar with a channel switcher. Threads fill the phone width. The composer follows the visible viewport and includes padding for the iOS home bar. Capture the phone layout with bun test test/phone-ui.test.ts (requires Chrome). The PNG is saved to test/artifacts/phone-support-thread.png at 390 x 844.

In tests

import { App } from "@slack/bolt";
import { SlackMock } from "@desplega.ai/slack-mock";

const slack = await SlackMock.start({ port: 0, manifest: "slack-manifest.json" });
const app = new App({
  token: slack.env.SLACK_BOT_TOKEN,
  appToken: slack.env.SLACK_APP_TOKEN,
  socketMode: true,
  clientOptions: { slackApiUrl: slack.env.SLACK_API_URL },
});
await app.start();
await slack.waitForConnection();

const ask = await slack.postMessage({ channel: "general", user: "alice", text: `<@${slack.bot.userId}> hi` });
const reply = await slack.waitForMessage({ channel: "general", thread_ts: ask.ts, from: "bot" });

What tests can do:

humans dopostMessage (text, thread, files), editMessage, deleteMessage, addReaction, removeReaction, slashCommand, clickButton, submitView, startAssistantThread, changeAssistantContext, addUser, addChannel, openDm, invite
observemessages, thread, ephemeralMessages, findMessages, waitForMessage, apiCalls, waitForApiCall, deliveries (every envelope and its ack), assistantThread
break thingsinjectFault({ method, error, httpStatus, retryAfterSec, extra }), disconnectSockets("refresh_requested"), options ackTimeoutMs, maxRetries, triggerIdTtlMs, echoBotMessages, subscribedEvents

The same operations exist over HTTP under /mock/* for use from another process (see docs/design.md).

HTTP mode (signed requests)

For apps that take Slack's HTTP transport, the mock can post deliveries to request URLs instead of pushing them over a WebSocket. Every request carries X-Slack-Request-Timestamp and an X-Slack-Signature (v0= HMAC-SHA256 of v0:<timestamp>:<raw body>) made with the signing secret you give it.

slack-mock serve --http-events-url http://127.0.0.1:3013/slack/events --signing-secret "$SLACK_SIGNING_SECRET" \
  [--http-interactivity-url URL] [--http-commands-url URL] [--http-retry-delays-ms 1000,5000,30000]
const slack = await SlackMock.start({
  httpDelivery: { eventsUrl, signingSecret, interactivityUrl, commandsUrl, retryDelaysMs: [1000, 5000], timeoutMs: 3000 },
});
DeliveryRequest
Events APIJSON body (the event_callback payload). Redelivered on a non-2xx, a timeout (timeoutMs, default 3s) or a refused connection, with X-Slack-Retry-Num and X-Slack-Retry-Reason (http_error, http_timeout, connection_failed), after each retryDelaysMs entry (default 1 min, 5 min, 30 min).
Slash commandsForm-encoded fields. Posted once. A JSON response body is the ack payload.
InteractivityForm with one payload field. Posted once. A JSON response body is the ack payload.

Interactivity and slash commands go to eventsUrl unless their own URL is set. deliveries() lists every request with its attempts and ack status. new HttpDelivery(...).verifyUrl() runs the url_verification challenge. Socket Mode stays the default; with httpDelivery set no app connection is needed.

Frames from a journal

A run recorded with dataFile can be replayed into one PNG per event that touched a thread (or, without a thread, a channel): the view after each message.add, message.update, message.delete, reaction.add and reaction.remove line, in journal order, plus final-thread.png (a copy of the last frame) and final-desktop.png (the full UI with the thread panel, 1280x900). Nothing is served: the journal is replayed in memory and each page is screenshotted from a temp file by the same headless Chrome helper as screenshot. A file.add line carries no message, so a shared file first appears in the frame of the message that shares it.

slack-mock frames --journal run.jsonl --channel C0GENERAL0 --thread 1788518410.672000 --out ./frames
#  5	message.add	/abs/frames/01-message.add.png
#  6	reaction.add	/abs/frames/02-reaction.add.png
#  ...
#  final-thread=/abs/frames/final-thread.png
#  final-desktop=/abs/frames/final-desktop.png

Leave out --thread for the channel view. --manifest app.json (bot display name), --width, --height and --no-desktop are optional. The same from code:

import { frames } from "@desplega.ai/slack-mock";

const result = await frames({ journal: "run.jsonl", channel: "general", thread: ask.ts, out: "./frames" });
// result.frames[1] = { index: 6, kind: "reaction.add", path: "/abs/frames/02-reaction.add.png" }
// result.finalThread, result.finalDesktop

Stitch the PNGs into a GIF with ffmpeg if you want motion; the mock only produces frames.

Slack behaviour that is modelled

  • Socket Mode: apps.connections.open, hello, envelopes for events_api, slash_commands and interactive, acks with response payloads, redelivery with retry_attempt when an envelope is not acked, server pings, disconnect messages, reconnects.
  • Events: message (channels, groups, im), app_mention, message_changed, message_deleted, file_share, reaction_added/removed, member_joined_channel, assistant_thread_started, assistant_thread_context_changed. The bot's own messages are echoed back like Slack does (Bolt's ignoreSelf drops them).
  • Web API: auth, chat (post, update, delete, ephemeral, permalink, streams, markdown_text kept as the message text and rendered as Markdown, markdown_text_conflict next to text or blocks), conversations (list, info, create, join, invite, leave, archive, history, replies, members, open), users (info, list, lookupByEmail), reactions, files (info, upload v2 flow, download with bearer token), views (open, update, push), assistant.threads (status, title, prompts), agents.sessions.setStatus, pins, bots, team.
  • UI: Block Kit markdown blocks render as Markdown (headings, bold, italic, strike, links, lists, code); table and data_table render as tables (column_settings alignment, data_table pages at page_size, default 5); data_visualization renders pie, bar, line and area charts as SVG. Their documented limits (rows, cells, caption, 2 charts per message, chart labels matching axis_config.categories) reject with invalid_blocks on chat.postMessage and chat.update. Unknown blocks show a gray unsupported: <type> box. Errors come back as { ok: false, error } with the real codes (not_in_channel, already_reacted, message_not_found, name_taken, expired_trigger_id, message_not_in_streaming_state, ...).
  • Slash command and interactive response_urls are served by the mock (ephemeral / in_channel, replace_original, delete_original).

Commands

bun test                      # unit and integration tests (real Bolt client)
bun run test:e2e              # boots ../agent-swarm against the mock (AGENT_SWARM_REPO to override; needs agent-swarm PR #1310)
bun run typecheck
bun run lint
bun run build                 # dist/ (JS for Bun + .d.ts). CI publishes to npm when the version in package.json changes on main

Hosted demo

https://demo.slack-mock.dev runs scripts/demo-server.ts: the mock plus a small demo bot (scripts/demo-bot.ts) in one container, deployed to Fly.io from main by .github/workflows/deploy.yml (fly.toml, Dockerfile). State lives in a JSONL journal on a volume and resets daily. Run it yourself:

docker build -t slack-mock-demo . && docker run -p 8080:8080 -e ADMIN_AUTH=demo:secret slack-mock-demo

PUBLIC_URL, ADMIN_AUTH (basic auth for the UI and /mock/*), DATA_FILE, RESET_EVERY_HOURS and MANIFEST configure it. The same knobs exist on the CLI (--public-url, --auth) and as SlackMockOptions (publicUrl, adminAuth).

The same image also serves a workspace for an external bot, the Agent Swarm demo at https://swarm-demo.slack-mock.dev (fly.swarm.toml, app slack-mock-swarm). Knobs that make that possible, all read by scripts/demo-server.ts:

EnvEffect
DEMO_BOT=offSkip the built-in demo bot; some other Bolt app connects over Socket Mode.
SLACK_MOCK_BOT_TOKEN, SLACK_MOCK_APP_TOKENThe tokens the app must present (botToken / appToken options). The defaults are public, so set these on any shared host.
APP_NAMEBot display name (appName).
SEED_FILEJSON with users, channels, messages and driver.prompts (scripts/seed.ts); replaces the built-in #general and is applied whenever the journal has no channels, so the workspace comes back after every reset. seeds/agent-swarm-demo.json is the shipped example.
UI_PUBLIC=trueKeep / and /c/... readable without credentials while ADMIN_AUTH still gates /mock/* (publicUi option). The composer then shows a sign-in instead of the browser's basic-auth prompt.
PRESENTER_AUTHuser:password that permits POST /mock/messages, POST /mock/reactions, and GET /mock/presenter (presenterAuth option). A presenter can post messages and manage reactions without the admin credential. Opening /c/<channel>#presenter=user:password signs that browser in.
DRIVER_INTERVAL_MINUTESEvery N minutes post the next driver.prompts entry as that user, mentioning the bot, while an app is connected (scripts/driver.ts). 0 disables it.

Docs

  • skills/slack-mock/SKILL.md: how to use the package from another repo (agents and humans).
  • docs/design.md: architecture, decisions, status.
  • docs/research/: the research behind it (agent-swarm's inbound/outbound Slack surface, Bolt and socket-mode internals, payload shapes, protocol docs and prior art, e2e conventions, Block Kit surface).

版本与发布记录

@desplega.ai/slack-mock 共发布 9 个版本,首次发布于 2026/09/03;过去 12 个月发布了 9 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.0latest2026/11/084 天前
0.5.02026/10/3013 天前
0.4.12026/10/1527 天前
0.4.02026/09/102 个月前
0.3.02026/09/102 个月前
0.2.02026/09/042 个月前

关于 @desplega.ai/slack-mock 的常见问题

@desplega.ai/slack-mock 是什么?

Mock Slack server (Web API + Socket Mode) for end-to-end testing Slack bots

如何安装 @desplega.ai/slack-mock?

在项目目录运行 npm install @desplega.ai/slack-mock,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 slack-mock,可以用 npx slack-mock 直接运行。

@desplega.ai/slack-mock 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@desplega.ai/slack-mock 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@desplega.ai/slack-mock 可以商用吗?

@desplega.ai/slack-mock 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@desplega.ai/slack-mock 的下载量有多少?

根据 npm 官方下载统计,@desplega.ai/slack-mock 在 2026/10/12 – 2026/11/10 这 30 天内被下载 34,798 次,日均约 1,160 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@desplega.ai/slack-mock 还在维护吗?

最新版本 0.6.0 发布于 2026/11/08(4 天前);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/09/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。