@chat-adapter/tests
v4.30.0
Published
Vitest factories, matchers, and setup utilities for testing Chat SDK adapters and bots
Readme
@chat-adapter/tests
Vitest factories, matchers, and setup utilities for testing Chat SDK adapters and bots.
Installation
pnpm add -D @chat-adapter/testsThis package has chat and vitest as peer dependencies — they should already be in your project.
Factories
import {
createMockAdapter,
createMockChatInstance,
createMockState,
createTestMessage,
mockLogger,
} from "@chat-adapter/tests";createMockAdapter(name?, overrides?)
Returns an Adapter with every method as vi.fn() and sensible defaults. Pass overrides to swap individual methods:
const adapter = createMockAdapter("slack", {
postMessage: vi.fn().mockResolvedValue({ id: "msg-7", raw: {} }),
});createMockState()
Returns a StateAdapter backed by in-memory Maps — subscriptions, locks, KV, lists, and queues all work end-to-end. Includes a cache: Map<string, unknown> for direct inspection.
createMockChatInstance(options?)
Returns a ChatInstance with every process* handler as vi.fn(). Useful for adapter authors verifying their adapter dispatches incoming events through the right hook.
const state = createMockState();
const chat = createMockChatInstance({ state });
await myAdapter.handleWebhook(req); // your adapter under test
expect(chat.processMessage).toHaveBeenCalledOnce();createTestMessage(id, text, overrides?)
Builds a Message with parsed markdown AST already wired up.
mockLogger / createMockLogger()
mockLogger is a shared Logger for tests that don't care about isolation. createMockLogger() returns a fresh one per call.
Matchers
Vitest custom matchers covering the most common Chat SDK assertions.
Auto-register via setup file
// vitest.config.ts
import { defineConfig } from "vitest/config";
export default defineConfig({
test: {
setupFiles: ["@chat-adapter/tests/setup"],
},
});Manual registration
import { matchers } from "@chat-adapter/tests/matchers";
expect.extend(matchers);Available matchers
| Matcher | Asserts |
|---------|---------|
| expect(adapter).toHavePosted(threadId, textPattern?) | adapter.postMessage was called for this thread (and message text matches textPattern if given) |
| expect(adapter).toHaveEdited(threadId, messageId, textPattern?) | adapter.editMessage was called for this message (and text matches textPattern if given) |
| expect(adapter).toHaveDeleted(threadId, messageId) | adapter.deleteMessage was called for this message |
| expect(adapter).toHaveReactedWith(threadId, messageId, emoji) | adapter.addReaction was called with the emoji (string or EmojiValue.name) |
| expect(adapter).toHaveStartedTyping(threadId) | adapter.startTyping was called for this thread |
| expect(adapter).toHavePostedToChannel(channelId, textPattern?) | adapter.postChannelMessage was called for this channel |
| expect(chat).toHaveDispatched(handler) | The named process* handler on the mock ChatInstance was called |
| expect(state).toBeSubscribedTo(threadId) | state.isSubscribed(threadId) resolves to true (async — needs await) |
expect(adapter).toHavePosted("slack:C1:t1", /hello/);
expect(adapter).toHaveEdited("slack:C1:t1", "msg-1", /updated/);
expect(adapter).toHaveDeleted("slack:C1:t1", "msg-1");
expect(adapter).toHaveReactedWith("slack:C1:t1", "msg-1", "thumbsup");
expect(adapter).toHaveStartedTyping("slack:C1:t1");
expect(adapter).toHavePostedToChannel("slack:C1");
expect(chat).toHaveDispatched("processMessage");
await expect(state).toBeSubscribedTo("slack:C1:t1");Text-pattern matchers (toHavePosted, toHaveEdited, toHavePostedToChannel) extract a comparable string from AdapterPostableMessage — handling plain strings, PostableMarkdown.markdown, PostableRaw.raw, and PostableCard.fallbackText. AST-shaped messages (PostableAst) and cards without fallbackText aren't text-matchable; assert without textPattern and inspect mock.calls directly for deeper checks.
Audience
- Bot authors — drive simulated events through your handlers, assert on outbound calls.
- Adapter authors — verify your
Adapterimplementation routes webhooks through the rightChatInstance.process*hook with the right normalized payload.
Adapter-specific helpers (e.g. signed Slack webhook builders, Teams claim builders) live in each adapter's own /testing subpath, not in this kit.
