@cogitator-ai/test-utils

中文文档
0.15.0发布于 2026/11/04MITTypeScriptES Module近 30 天下载 9585

Shared testing utilities for Cogitator packages

原始使用文档
文档目录23 节
  1. Installation
  2. Features
  3. Quick Start
  4. Testing with MockLLMBackend
  5. Testing with MockMemoryAdapter
  6. Mocks
  7. MockLLMBackend
  8. MockMemoryAdapter
  9. Fixtures
  10. Agents
  11. Messages and Tool Calls
  12. Tools
  13. Run Results
  14. Helpers
  15. Stream Helpers
  16. Async Helpers
  17. Conformance
  18. Backend Conformance Suite
  19. Fake Provider Server
  20. Wire Protocols & Encodings
  21. Memory adapters and embedding stores
  22. Public Exports
  23. License

@cogitator-ai/test-utils

Testing utilities for Cogitator to test your own agents, tools, workflows, and backends without calling real LLMs or databases.

Installation

pnpm add -D @cogitator-ai/test-utils

@cogitator-ai/test-utils gives you deterministic stand-ins and helpers to test your own agents and applications without network calls, paid API keys, or external databases. See Test Utilities on the website.

Features

  • Mock LLM Backend - Scriptable MockLLMBackend for deterministic chat() responses and chatStream() chunks with request recording and inspection
  • Mock Memory Adapter - In-process MockMemoryAdapter recording method calls for threads and entries
  • Mock Embedding Service - Deterministic MockEmbeddingService: stable unit vectors from a hashed bag of words, pin-able per text, recording every call
  • Test Fixtures - Preconfigured agents, messages, tool calls, tool results, JSON Schema tools, and run results
  • Stream Helpers - Drain and collect stream content, chunks, and tool calls, or create mock byte streams
  • Async Utilities - Polling (waitFor, eventually), delay, and promise timeout helpers
  • Backend Conformance Kit - Test custom backends against wire protocols (OpenAI, Anthropic, Gemini, Ollama, Bedrock) with startFakeProvider
  • Memory Conformance Suites - Hold memory adapters, memory item stores, memory file stores and embedding stores of your own to the contract of the built-in ones

Quick Start

Testing with MockLLMBackend

A short, valid Vitest example using MockLLMBackend:

import { describe, it, expect } from 'vitest';
import { MockLLMBackend, createToolCall } from '@cogitator-ai/test-utils';

describe('MockLLMBackend', () => {
  it('returns scripted responses and inspects requests', async () => {
    const backend = new MockLLMBackend();
    backend.setResponses([
      { toolCalls: [createToolCall('get_weather', { city: 'Tokyo' })] },
      { content: 'The weather in Tokyo is sunny and 22°C.' },
    ]);

    const first = await backend.chat({
      model: 'test-model',
      messages: [{ role: 'user', content: 'What is the weather in Tokyo?' }],
    });

    expect(first.finishReason).toBe('tool_calls');
    expect(first.toolCalls?.[0].name).toBe('get_weather');

    const second = await backend.chat({
      model: 'test-model',
      messages: [
        { role: 'user', content: 'What is the weather in Tokyo?' },
        { role: 'assistant', content: 'Checking weather...' },
        {
          role: 'tool',
          content: '{"temperature": 22}',
          toolCallId: first.toolCalls![0].id,
          name: 'get_weather',
        },
      ],
    });

    expect(second.content).toBe('The weather in Tokyo is sunny and 22°C.');
    expect(second.finishReason).toBe('stop');

    expect(backend.getCallCount()).toBe(2);
    expect(backend.wasCalledWith((req) => req.model === 'test-model')).toBe(true);
    expect(backend.getLastCall()?.messages).toHaveLength(3);
  });
});

Testing with MockMemoryAdapter

A short, valid Vitest example using MockMemoryAdapter:

import { describe, it, expect } from 'vitest';
import { MockMemoryAdapter, createUserMessage } from '@cogitator-ai/test-utils';

describe('MockMemoryAdapter', () => {
  it('stores conversation history and records calls', async () => {
    const memory = new MockMemoryAdapter();
    await memory.connect();
    expect(memory.isConnected()).toBe(true);

    const threadResult = await memory.createThread('agent-1', { topic: 'testing' }, 'thread-123');
    expect(threadResult.success).toBe(true);

    const addResult = await memory.addEntry({
      threadId: 'thread-123',
      message: createUserMessage('Remember this note'),
      tokenCount: 4,
    });
    expect(addResult.success).toBe(true);

    const entriesResult = await memory.getEntries({ threadId: 'thread-123' });
    expect(entriesResult.success).toBe(true);
    if (entriesResult.success) {
      expect(entriesResult.data).toHaveLength(1);
      expect(entriesResult.data[0].message.content).toBe('Remember this note');
    }

    expect(memory.getCallsFor('createThread')).toHaveLength(1);
    expect(memory.getCallsFor('addEntry')).toHaveLength(1);

    await memory.disconnect();
    expect(memory.isConnected()).toBe(false);
  });
});

Mocks

MockLLMBackend

MockLLMBackend implements LLMBackend (provider: 'openai') and answers calls with scripted responses or stream chunks while recording all incoming requests.

Scripting Chat Responses

import { MockLLMBackend, createMockLLMBackend, createToolCall } from '@cogitator-ai/test-utils';

const backend = createMockLLMBackend();

// Set a single response for all chat() calls
backend.setResponse({ content: 'Hello, world!' });

// Set multiple responses returned in sequence (the last response repeats)
backend.setResponses([{ content: 'First response' }, { content: 'Second response' }]);

// Shortcut for scripted tool call responses
backend.setToolCallResponse([createToolCall('calculator', { expression: '2 + 2' })]);

// Set default delay (in ms) before answering
backend.setDefaultDelay(50);

Streaming

import { MockLLMBackend, collectStreamContent } from '@cogitator-ai/test-utils';

const backend = new MockLLMBackend();

// Script a stream for chatStream()
backend.setStreamChunks([{ content: 'Hello' }, { content: ' world' }, { content: '!' }]);

// Or script multiple distinct streams in sequence
backend.setMultipleStreamChunks([[{ content: 'First stream' }], [{ content: 'Second stream' }]]);

const stream = backend.chatStream({
  model: 'test-model',
  messages: [{ role: 'user', content: 'Hi' }],
});

const text = await collectStreamContent(stream);

Request Inspection

// Inspect recorded requests
const count = backend.getCallCount();
const allCalls = backend.getCalls();
const lastCall = backend.getLastCall();

// Predicate match
const wasCalled = backend.wasCalledWith((req) => req.model === 'test-model');

// Reset calls, responses, and stream chunks
backend.reset();

MockMemoryAdapter

MockMemoryAdapter implements MemoryAdapter (provider: 'memory') in memory using native Maps and records every invocation.

import { MockMemoryAdapter, createMockMemoryAdapter } from '@cogitator-ai/test-utils';

const memory = createMockMemoryAdapter();

// Lifecycle
await memory.connect();
const connected = memory.isConnected(); // true

// Threads
const thread = await memory.createThread('agent-id', { tag: 'session' }, 'custom-id');
const fetchedThread = await memory.getThread('custom-id');
await memory.updateThread('custom-id', { tag: 'updated' });

// Entries
await memory.addEntry({
  threadId: 'custom-id',
  message: { role: 'user', content: 'Hello' },
  tokenCount: 1,
});
const entries = await memory.getEntries({ threadId: 'custom-id', limit: 10 });
const singleEntry = await memory.getEntry(
  entries.success && entries.data[0] ? entries.data[0].id : ''
);
await memory.deleteEntry('entry-id');
await memory.clearThread('custom-id');
await memory.deleteThread('custom-id');

// Inspection
const calls = memory.getCalls(); // Array<{ method: string; args: unknown[] }>
const threadCalls = memory.getCallsFor('createThread');

// Reset all internal state and calls
memory.reset();
await memory.disconnect();

Fixtures

Agents

Generate test agent configs or agent-shaped objects with test defaults:

import { createTestAgentConfig, createTestAgent } from '@cogitator-ai/test-utils';

// AgentConfig with default name 'test-agent' and model 'mock/test-model'
const config = createTestAgentConfig({
  name: 'custom-agent',
  instructions: 'Custom instructions',
});

// Agent-shaped object implementing the Agent interface
const agent = createTestAgent({
  name: 'test-agent',
});

Messages and Tool Calls

Construct strongly typed conversation messages, tool calls, and tool results:

import {
  createUserMessage,
  createAssistantMessage,
  createSystemMessage,
  createToolMessage,
  createMultimodalMessage,
  createImageUrlMessage,
  createToolCall,
  createToolResult,
  createConversation,
} from '@cogitator-ai/test-utils';

const userMsg = createUserMessage('Hello');
const assistantMsg = createAssistantMessage('Hi there');
const systemMsg = createSystemMessage('You are a test assistant');
const toolMsg = createToolMessage('call_1', '{"result": 42}', 'calculator');

const multimodalMsg = createMultimodalMessage([
  { type: 'text', text: 'Describe this' },
  { type: 'image_url', image_url: { url: 'https://example.com/image.png' } },
]);
const imgMsg = createImageUrlMessage('Look at this', 'https://example.com/image.png');

const call = createToolCall('calculator', { expression: '2 + 2' });
const result = createToolResult(call, { result: 4 });

const turns = createConversation(
  { role: 'user', content: 'Hello' },
  { role: 'assistant', content: 'Hi' }
);

Tools

Predefined JSON Schema tool fixtures for testing tool execution and schema passing:

import {
  createTestTool,
  createCalculatorTool,
  createWeatherTool,
  createFailingTool,
  createSlowTool,
  createToolSchema,
} from '@cogitator-ai/test-utils';

// Customizable SimpleTool
const customTool = createTestTool({
  name: 'custom_tool',
  description: 'A custom tool',
  execute: async (args) => ({ echo: args }),
});

// Built-in tools
const calc = createCalculatorTool(); // evaluates arithmetic expressions ('2 + 2')
const weather = createWeatherTool(); // returns 22°C sunny weather for city
const failing = createFailingTool('Custom error'); // always throws
const slow = createSlowTool(50); // delays for specified ms before returning

// ToolSchema fixture
const schema = createToolSchema('my_tool', 'Tool description');

Run Results

Mock RunResult structures for code consuming agent execution outcomes:

import {
  createMockRunResult,
  createRunResultWithToolCalls,
  createRunResultWithMessages,
} from '@cogitator-ai/test-utils';

const baseResult = createMockRunResult('Final answer');
const withTools = createRunResultWithToolCalls('Output', [call]);
const withHistory = createRunResultWithMessages('Output', [userMsg, assistantMsg]);

Helpers

Stream Helpers

Helpers to consume and test asynchronous generator streams (AsyncGenerator<ChatStreamChunk>):

import {
  collectStreamContent,
  collectStreamChunks,
  collectStreamToolCalls,
  createMockReadableStream,
} from '@cogitator-ai/test-utils';

// Drains stream and returns concatenated text
const content = await collectStreamContent(stream);

// Drains stream and returns all chunks as an array
const chunks = await collectStreamChunks(stream);

// Drains stream and returns parsed tool calls
const toolCalls = await collectStreamToolCalls(stream);

// Creates a ReadableStream<Uint8Array> from string chunks (e.g. for SSE testing)
const byteStream = createMockReadableStream(['data: {"text":"hello"}\n\n']);

Async Helpers

Utilities for handling asynchronous timing, polling, and timeouts:

import { waitFor, eventually, delay, withTimeout } from '@cogitator-ai/test-utils';

// Sleep for ms
await delay(100);

// Poll condition until truthy or timeout (defaults: timeout 5000ms, interval 50ms)
const ready = await waitFor(() => checkReady(), { timeout: 2000, interval: 20 });

// Retry assertion until it passes or timeout
await eventually(() => expect(isDone()).toBe(true), { timeout: 3000, interval: 50 });

// Wrap promise with timeout
const value = await withTimeout(fetchData(), 1000);

Conformance

A complete kit for verifying that custom or third-party LLMBackend implementations correctly adhere to provider protocols, error handling, cancellation, and streaming conventions.

Backend Conformance Suite

Run the full suite of conformance test cases against an LLMBackend:

import { describe, it } from 'vitest';
import {
  backendConformanceCases,
  openAIChatWire,
  type BackendConformanceTarget,
} from '@cogitator-ai/test-utils';

const target: BackendConformanceTarget = {
  name: 'MyCustomBackend',
  model: 'gpt-4o',
  wire: openAIChatWire,
  createBackend: (url) => new MyCustomBackend({ baseUrl: url }),
};

describe('MyCustomBackend conformance', () => {
  for (const c of backendConformanceCases(target)) {
    (c.skip ? it.skip : it)(c.name, () => c.run());
  }
});

Fake Provider Server

Run a lightweight HTTP server emulating provider APIs:

import { startFakeProvider, sse } from '@cogitator-ai/test-utils';

const server = await startFakeProvider();

server.reply((req) => {
  if (req.path === '/v1/chat/completions') {
    return {
      type: 'stream',
      contentType: 'text/event-stream',
      chunks: [sse({ choices: [{ delta: { content: 'Hi' } }] })],
    };
  }
  return { type: 'json', status: 404, body: { error: 'Not found' } };
});

console.log(`Fake provider listening at: ${server.url}`);

await server.close();

Wire Protocols & Encodings

Wire specifications and formatting helpers for popular LLM provider protocols:

  • Wires: openAIChatWire, openAIResponsesWire, anthropicWire, geminiWire, ollamaWire, bedrockConverseWire
  • Encodings: sse(data, event?), ndjson(data), awsEventStreamMessage(headers, payload)
  • Fixture: conformanceRequest, CONFORMANCE_FIXTURE

Memory adapters and embedding stores

memoryAdapterConformanceCases(target), memoryItemAdapterConformanceCases(target), memoryFileAdapterConformanceCases(target) and embeddingAdapterConformanceCases(target) check a MemoryAdapter, a MemoryItemAdapter, a MemoryFileAdapter or an EmbeddingAdapter against the contract the built-in ones keep: missing versus null, atomic updateThread, entry order with equal timestamps, limits, cascades, copies, listThreads filters and paging, nearest-first search, thresholds, filters and deletes.

for (const c of memoryAdapterConformanceCases({
  create: async () => {
    const adapter = new MyAdapter();
    await adapter.connect();
    return adapter;
  },
  release: (adapter) => adapter.disconnect().then(() => undefined),
})) {
  (c.skip ? it.skip : it)(c.name, () => c.run());
}

Each case uses threads, agents or embeddings of its own, so one database can serve them all. The embedding suite also takes dimensions. In the memory file suite file-find-heads checks findFileHeads: the newest version of each scope and path, deletions included, a version placed off the branch left out, oldest first, and nothing for an adapter without it, and file-withdraw checks withdrawFileVersions: a withdrawn version keeps its sequence and is never a head. In the memory adapter suite redacts-an-entry-in-place checks redactEntry: a redacted entry keeps its place, role and tool call ids, keeps nothing of what it said in its text, tool arguments or tool results, records metadata.redacted, gives null when missing and is reported once as entry-redacted, and, on an adapter with branches, that it keeps its parent (also once the parent is deleted) and that an event entry is refused (nothing is checked for an adapter without redactEntry or onChange). The memory item suite checks that items and entities read back field for field (dates as Dates, a missing field absent, never null), replace by id, findItems filters, scope filters (visibleTo, scope) and order (oldest recorded first), items let go left out unless includeExpired while one let go later is still found, entities found by any normalized name, deletes and copies. The memory file suite checks that a version of a memory file reads back field for field (a deletion's null content included), that a version in a place another one holds (scope, path and sequence) is refused with MEMORY_FILE_CONFLICT, also when writers race for one place, that a refused batch keeps none of its versions, the filters (ids, paths, prefix, visibleTo, scope) and their order (by createdAt, then sequence, then id), redaction (content and sha256 replaced, nothing else changed), deletes and copies.


Public Exports

Public exports are grouped in src/index.ts as:

GroupModuleDescription
Mocks@cogitator-ai/test-utils (./mocks)MockLLMBackend, createMockLLMBackend, MockMemoryAdapter, createMockMemoryAdapter, MockChatResponse, MockStreamChunk
Fixtures@cogitator-ai/test-utils (./fixtures)createTestAgent, createTestAgentConfig, TestAgentConfig, createUserMessage, createAssistantMessage, createSystemMessage, createToolMessage, createMultimodalMessage, createImageUrlMessage, createToolCall, createToolResult, createConversation, createTestTool, createCalculatorTool, createWeatherTool, createFailingTool, createSlowTool, createToolSchema, TestToolOptions, SimpleTool, createMockRunResult, createRunResultWithToolCalls, createRunResultWithMessages, TestRunResultOptions
Helpers@cogitator-ai/test-utils (./helpers)collectStreamContent, collectStreamChunks, collectStreamToolCalls, createMockReadableStream, waitFor, eventually, delay, withTimeout
Conformance@cogitator-ai/test-utils (./conformance)startFakeProvider, FakeProvider, FakeProviderHandler, FakeProviderOptions, FakeProviderReply, FakeProviderRequest, backendConformanceCases, conformanceRequest, CONFORMANCE_FIXTURE, BackendConformanceCase, BackendConformanceTarget, ConformanceMode, ConformanceScenario, ProviderWire, openAIChatWire, openAIResponsesWire, anthropicWire, geminiWire, ollamaWire, bedrockConverseWire, sse, ndjson, awsEventStreamMessage, memoryAdapterConformanceCases, memoryItemAdapterConformanceCases, memoryFileAdapterConformanceCases, embeddingAdapterConformanceCases and their *ConformanceCase, *ConformanceTarget and *Scenario types

License

MIT

版本与发布记录

@cogitator-ai/test-utils 共发布 79 个版本,首次发布于 2026/01/27;过去 12 个月发布了 79 个版本。

版本发布日期距今
0.15.0latest2026/11/046 天前
0.14.72026/10/2912 天前
0.14.62026/10/2912 天前
0.14.52026/10/2912 天前
0.14.42026/10/2912 天前
0.14.32026/10/2812 天前

关于 @cogitator-ai/test-utils 的常见问题

@cogitator-ai/test-utils 是什么?

Shared testing utilities for Cogitator packages

如何安装 @cogitator-ai/test-utils?

在项目目录运行 npm install @cogitator-ai/test-utils,或使用 pnpm add / yarn add / bun add。

@cogitator-ai/test-utils 支持 TypeScript 吗?

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

@cogitator-ai/test-utils 支持 ESM 还是 CommonJS?

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

@cogitator-ai/test-utils 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22.12.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cogitator-ai/test-utils 可以商用吗?

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

@cogitator-ai/test-utils 的下载量有多少?

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

@cogitator-ai/test-utils 还在维护吗?

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