---
title: "Testing"
description: "Test agents, channels, streaming, tools, and Hono routes offline with deterministic mocks."
---

> Documentation Index
> Fetch the complete documentation index at: https://xsaf.ilha.build/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing

XSAF's public mock adapters keep tests deterministic and ensure default test suites do not require network access or AI tokens.

- **Test without tokens** — Test model requests, memory, channels, and lifecycle without mocking private modules.
- **Exercise HTTP in memory** — Call `bot.app.request()` or `bot.fetch()` with web-standard requests instead of opening a port.

## Test a channel round trip

```ts title="agent.test.ts"
import { expect, test } from "bun:test";
import { agent } from "@xsaf/agent";
import mockChannel from "@xsaf/agent/channel/mock";
import mockModel from "@xsaf/agent/model/mock";

test("replies through a channel", async () => {
  const model = mockModel({ response: "mock reply" });
  const channel = mockChannel();
  const bot = agent({
model,
persona: "Test agent",
stream: false,
  }).channel(channel);

  await bot.start();
  try {
await channel.receive({ sessionId: "test", text: "hello" });
expect(channel.sent[0]?.payload).toBe("mock reply");
  } finally {
await bot.stop();
  }
});
```

`model.requests` records each request. Use it to assert message history, available tools, reasoning, and step configuration without mocking private modules.

## Return responses dynamically

```ts
const model = mockModel({
  response(request) {
const latest = request.messages.at(-1)?.content;
return { text: `received:${latest}` };
  },
});
```

`response` accepts a static string, a `ModelResponse`, or a callback. The mock model implements non-streaming generation only; when agent streaming is enabled, XSAF falls back to that method. Supply a small custom `XsafModelAdapter.stream()` implementation when a test must control lazy chunks and completion behavior.

## Test Hono without a listener

```ts
const response = await bot.app.request("http://localhost/invoke", {
  method: "POST",
  headers: {
host: "localhost",
"content-type": "application/json",
  },
  body: JSON.stringify({ sessionId: "http-test", prompt: "hello" }),
});

expect(response.status).toBe(200);
expect(await response.json()).toEqual({ text: "mock reply" });
```

The explicit `host` header satisfies the Hono/MCP host protection used by the shared application.

## Test MCP without network calls

`mcp({ fetch })` accepts an injected fetch-compatible function. Return mocked JSON-RPC responses for `tools/list`, `tools/call`, resource reads, and prompt retrieval. This seam tests client protocol behavior without a live server.

## Test executable tools

Tools still require an explicit sandbox in tests. When the function is trusted test code:

```ts
import local from "@xsaf/agent/sandbox/local";

const bot = agent(config)
  .sandbox(local({ unsafe: true }))
  .tool(tool);
```

This is a deliberate no-isolation adapter, not a production sandbox.

## Verify lifecycle

Always stop started agents in `finally`. Add focused tests for:

- no driver work before `.start()`
- same-session serialization and cross-session concurrency
- partial-startup cleanup
- reverse-order shutdown with aggregated errors
- validation and approval before execution
- timeout and cancellation without retries
- complete stream consumption and memory persistence

## Next steps

- [Tool pipeline](/recipes/tools)
- [Sessions, streaming, and memory](/recipes/sessions-memory#sessions-and-streaming)
- [Tools & Security](/recipes/tools#sandbox-security)
- [Roadmap](/roadmap)

Source: https://xsaf.ilha.build/recipes/testing/index.mdx
