v0.1.2 — open source — MIT licence

Testing utilities
for Slack bots.

Block Kit validation and SDK mocking. Catch invalid blocks before they reach Slack. Unit test your handlers without a real workspace or token.

View on npm Read the docs
Install: npm install botlint-slack --save-dev
29
tests passing
0
runtime dependencies
10
block types validated
7
API methods mocked

Block Kit validation

Slack returns 200 OK. Even when your blocks are wrong.

The metadata gets silently dropped. Your message appears as plain text. No error. No warning. botlint-slack validates your blocks offline — before the API ever sees them — with exact error messages including character counts and indices.

validate.test.js
// header text exceeds the 150 char limit
const result = validate([{
  type: 'header',
  text: {
    type: 'plain_text',
    text: 'x'.repeat(203)
  }
}]);
block[0].text exceeds 150 char limit (current: 203)
validate(validBlocks) → { valid: true, errors: [] }

SDK mocking

Unit test your handlers. No token needed.

createMockClient() is a drop-in replacement for app.client in Bolt handlers. Records every API call so you can assert on exactly what your bot sent. Zero network calls, zero credentials, runs in milliseconds.

handler.test.js
1
Create mock client
const mock = createMockClient()
2
Run your handler
await myHandler({ client: mock.client, ... })
3
Assert on calls
mock.lastCall('chat.postMessage')
.channel 'C123ABC'✓ pass
.text 'approved'✓ pass
network calls 0✓ pass

Documentation

Everything you need to start testing your Slack bot today.

validate(input)

Validates Slack Block Kit structures offline without hitting the API. Accepts arrays of blocks, single block objects, modal views, or home tab views. Always returns { valid: boolean, errors: string[] } — never throws.

  • Pass an array of blocks to validate each one with its index in error messages
  • Pass a single block object to validate just that block
  • Pass a modal view (type: 'modal') to validate the modal wrapper and all its blocks
  • Pass a home tab view (type: 'home') to validate the home tab structure
  • Unknown block types pass through silently — botlint-slack never throws
example.js
const { validate } = require('botlint-slack');

// Array of blocks
const result = validate([
  { type: 'section', text: { type: 'mrkdwn', text: 'Hello' } }
]);
// → { valid: true, errors: [] }

// Modal view
validate({
  type: 'modal',
  title: { type: 'plain_text', text: 'Settings' },
  callback_id: 'settings_modal',
  blocks: [...]
});

createMockClient()

Returns a mock object with four properties. Pass mock.client wherever your handler expects app.client. All methods resolve immediately with { ok: true }.

  • mock.client — drop-in for app.client, all methods are async and recorded
  • mock.calls — access as calls['chat.postMessage'] or calls.chat.postMessage
  • mock.lastCall(method) — returns args of most recent call, or null
  • mock.reset() — clears all recorded calls, use in beforeEach
handler.test.js
const { createMockClient } = require('botlint-slack');

describe('myHandler', () => {
  let mock;

  beforeEach(() => { mock = createMockClient(); });

  it('posts to correct channel', async () => {
    await myHandler({ client: mock.client, body: fakeBody, ack: jest.fn() });

    const post = mock.lastCall('chat.postMessage');
    expect(post.channel).toBe('C123ABC');
    expect(post.text).toContain('approved');
  });
});

Jest matchers

Custom matchers for cleaner assertions. Register once in your Jest setup file, then use across all tests.

jest.config.js

module.exports = { setupFilesAfterEnv: ['./jest.setup.js'] };

jest.setup.js

const setupMatchers = require('botlint-slack/jest');
setupMatchers();
Matcher Usage
toBeValidSlackBlocks() Asserts that an array of blocks passes validation. Fails with a full error list if invalid.
toBeValidSlackModal() Asserts that a modal view object passes validation including title, callback_id, and blocks.
toHavePostedTo(channel) Asserts a recorded call was sent to a specific channel ID.
toHaveText(text) Asserts a recorded call's text field equals or contains the given string.
toHaveBlocks() Asserts a recorded call included a non-empty blocks array.

Validated block types

botlint-slack validates all core Block Kit block types against Slack's actual constraints. All types also check block_id uniqueness and max length of 255 chars.

  • Unknown block types pass through as valid — botlint-slack never blocks unknown types
  • Error messages include the block index, field name, and violation detail
  • Modal and home tab views validate the wrapper structure plus all nested blocks
section
text ≤ 3000 • fields ≤ 10 items • each field ≤ 2000 chars
actions
1–5 elements • button label ≤ 75 chars • action_id ≤ 255
context
1–10 elements • text ≤ 2000 • image or text only
header
text ≤ 150 chars • plain_text only
modal
title ≤ 24 chars • callback_id required • submit/close ≤ 24
image
image_url & alt_text required • title ≤ 2000 chars
input
label & element required • hint ≤ 2000 chars
video
title, video_url, thumbnail_url, alt_text all required
rich_text
elements array required • basic structure validation

Mocked API methods

All methods are async and return resolved promises. No network calls are made. Calls are recorded in both flat and nested forms.

  • Access as mock.calls['chat.postMessage'] or mock.calls.chat.postMessage
  • Each entry in the calls array is the full args object passed to the method
  • Use mock.reset() in beforeEach to clear between tests
  • mock.lastCall(method) returns null if the method was never called
chat.postMessage
→ { ok: true, ts: '123.456' }
chat.update
→ { ok: true }
chat.delete
→ { ok: true }
views.open
→ { ok: true, view: { id: 'V123' } }
views.update
→ { ok: true }
views.push
→ { ok: true }
users.info
→ { ok: true, user: { id: args.user, name: 'testuser' } }

What developers are saying

"It would be very convenient if this library included a utility for validating blocks. Currently, blocks are a bit brittle — changes can produce errors which can only be debugged interactively through a dummy Slack channel."
bolt-js #1652 — 15 upvotes, open since Nov 2022
"I cannot find any framework pointers, or documentation, on how I would write reasonable unit tests for this."
bolt-python #380 — 31 upvotes
"At this moment, there is no built-in support for testing your code using this SDK."
Slack maintainer — python-slack-sdk #1188

Roadmap

V1 is live. V2 and V3 are gated on community traction — watch download numbers for 4–6 weeks after launch.

live
V1 — npm package
Block Kit validator with specific error messages, mock client for Bolt handlers, custom Jest matchers. Zero runtime dependencies. MIT licence. Works with Jest, Mocha, or any test runner.
validate() createMockClient() Jest matchers 10 block types 0 deps Node 18+
next
V2 — E2E testing
Send real messages to a test channel via the Slack Events API. Assert on real bot responses. Simulate button clicks and modal interactions. The hard problem: message correlation — knowing which response belongs to which test.
BotTester class tester.send().expect() button clicks modal interactions Slack Marketplace
later
V3 — cloud runner
Run E2E tests in CI without managing credentials locally. GitHub Actions integration, test history, pass/fail badges for your README. Hosted on Railway.
GitHub Actions test history README badges Slack failure alerts $19/month

Stop debugging in
real Slack channels.

Catch Block Kit errors before they ship. Unit test your handlers in milliseconds. No token, no workspace, no drama.

npm install botlint-slack Read the guide →