Structure for testability
Split every bot into three layers:
- Adapter: parses the platform payload into your own event type and turns your reply objects into API calls.
- Logic: pure functions or a state machine that takes an event plus state and returns replies and state changes.
- Effects: sending messages, database writes, external APIs.
The logic layer needs no network and no tokens, so it can be tested fast and exhaustively. The flow designer helps you map states before you write them.
Unit tests
Test commands, argument parsing, permissions and edge cases: empty text, very long text (see the message splitter), unknown commands, unexpected languages and emoji.
import { handle } from "../src/logic.js";
test("remind parses duration", () => {
const out = handle({ type: "command", name: "remind", args: "10m tea" }, {});
expect(out.schedule.delayMs).toBe(600000);
});
Recorded payloads
Save real (anonymized) webhook bodies as fixtures and replay them through the adapter. Include edited messages, callback queries, messages in groups, service messages and payloads with missing optional fields. Generate realistic samples with the webhook mock builder and inspect them with the JSONPath tester.
Fake the platform
- Point the API client at a local fake server, or inject a client interface and use an in-memory fake.
- Assert on the calls made: method, chat ID, text, keyboard.
- Simulate failures: 429 with retry-after, 5xx, timeouts. Check that retries stop and that nothing is sent twice.
Security tests
- Requests with a missing or wrong signature or secret token must get 401 and cause no effects.
- Old timestamps (replays) must be rejected for platforms that sign timestamps, such as Slack.
Conversation tests
Script multi-turn conversations ("user says, bot should reply something matching") and run them against the logic layer. For LLM features, assert on structure and intent rather than exact wording.
LLM evaluation sets
Keep a versioned file of inputs with expected properties: the right tool call, a refusal, a citation, a maximum length, no personal data. Run it on every change of prompt, model or retrieval settings and compare scores. Include adversarial inputs from prompt injection defense.
Staging and load
- Register a separate staging bot with its own token and test chats. Never test on the production bot.
- Load test your webhook endpoint, not the platform: replay fixtures at high rates against staging and watch latency and error rates.
In CI
Run unit and fixture tests on every push, block merges on failures and run evaluation sets nightly or before releases. See CI/CD for bots.