AI Agent Integration Tests
We test AI agents with @copilotkit/aimock, an HTTP mock server that speaks LLM provider protocols. Unlike in-process model stubs, it runs the real Provider class and lets you inspect the HTTP requests through its Journal.
Reference implementation: tests/integration/planner.test.ts.
Principles
Section titled “Principles”What we mock
Section titled “What we mock”- The AI provider — via the aimock HTTP server. Point the Vercel AI SDK at
mock.url/v1withcreateOpenAI({ compatibility: 'compatible' })andopenai.chat('model-name'). Thecompatibility: 'compatible'option keeps the mock on the Chat Completions API; without it, the SDK defaults to the Responses API, which aimock does not fully implement. - Explorer, StateManager, Researcher, ExperienceTracker — duck-typed mocks with only the methods the agent under test calls. Each agent runs in isolation; downstream agents (such as Researcher when testing Planner) return canned output.
withPage— resolves with a fake page object for agents that validate locators, since no browser runs in these tests.
What we don’t mock
Section titled “What we don’t mock”- The real
Providerclass, so tests exercise serialization, retry, and telemetry paths. - Parsers, result objects, and markdown processing — the real pipeline runs.
ActionResult.fromState(), which works without the filesystem whenstate.htmlis inline.
What we test
Section titled “What we test”- Output correctness: the plan or research markdown the agent returns matches the canned AI response.
- Prompt construction (via Journal):
mock.getLastRequest()returns the messages sent to the AI, so you assert on what the agent prompted, not just what it returned. This is the main reason to use aimock. - Control flow: cache hits, dedup, retries, error paths, and style or feature injection. Inspect the request count and prompt content.
Fixture types
Section titled “Fixture types”- Text response (for
chat()/invokeConversation()):mock.on({}, { content: 'text' }) - Structured output (for
generateObject()):mock.on({}, { content: JSON.stringify(obj) })— the SDK parses it back viaresponse_format: json_schema - Sequential responses:
mock.on({ sequenceIndex: 0 }, ...),sequenceIndex: 1, … - Errors:
mock.on({}, { error: {...}, status: 500 }) - See aimock docs for matching by user message, tool name, regex, or predicate.
Test data
Section titled “Test data”- Canned UI maps live in
test-data/ui-maps/. - Use fictional applications (Task Tracker, and so on). Never use real product data or user names.
- Two formats exist, depending on the agent under test:
- Planner-input (
task-board.md): Element and Type columns only — what the Planner sees after its own table-column filtering. - Researcher-output (
task-board-research.md): full Element, Type, ARIA, CSS, and Coordinates columns — the raw Researcher AI output format.
- Planner-input (
Module-level caches
Section titled “Module-level caches”Many agents hold module-level state: plan registry, session dedup, style cache, research cache. Each module that caches across calls must export a clearXxx() function for use in beforeEach. See the existing exports:
src/ai/planner/subpages.ts—clearPlanRegistry()src/ai/planner/session-dedup.ts—clearSessionDedup()src/ai/planner/styles.ts—clearStyleCache()src/ai/researcher/cache.ts—clearResearchCache()
Running
Section titled “Running”bun test tests/integration/planner.test.tsbun test tests/integration/