Skip to content

Testing

Agentick provides first-class testing utilities — mock adapters, agent rendering, mock apps, and test runners.

Test Adapter

createTestAdapter creates a mock model that returns scripted responses:

tsx
import { createTestAdapter } from "@agentick/core/testing";

const adapter = createTestAdapter({
  defaultResponse: "Hello! How can I help?",
});

respondWith

Queue exact responses for the next model call with automatic content detection:

tsx
const model = createTestAdapter();

// Simple text
model.respondWith(["Hello world"]);

// Text + tool call
model.respondWith([
  "Let me search for that",
  { tool: { name: "search", input: { query: "test" } } },
]);

// With reasoning
model.respondWith([{ reasoning: "Let me think..." }, "The answer is 42"]);

respondWith is consumed on the next call — subsequent calls fall back to defaultResponse.

Content Types

InputDetected As
"text"Text block
{ tool: { name, input } }Single tool call
{ tool: [...] }Parallel tool calls
{ image: { url } }Image from URL
{ reasoning: "..." }Reasoning/thinking block

Imperative Methods

tsx
model.setResponse("New default response");
model.setToolCalls([{ name: "foo", input: {} }]);
model.setError(new Error("Simulated failure"));

Assertions

tsx
const inputs = model.getCapturedInputs();
expect(inputs).toHaveLength(2);
expect(inputs[0].messages).toContainEqual(expect.objectContaining({ role: "user" }));

renderAgent

Full agent lifecycle testing with send, result inspection, and rerender:

tsx
import { renderAgent, act, cleanup } from "@agentick/core/testing";

afterEach(() => cleanup());

test("agent responds to user", async () => {
  const model = createTestAdapter();
  const { send, result } = renderAgent(MyAgent, { model });

  model.respondWith(["Hi there!"]);
  await act(() => send("Hello"));

  expect(result.current.lastAssistantMessage).toBe("Hi there!");
  expect(model.getCapturedInputs()).toHaveLength(1);
});

Options

OptionDescription
propsProps to pass to the agent component
modelCustom test model (default: createTestAdapter())
maxTicksMax ticks per execution (default: 10)

compileAgent

Test the compiled structure without execution:

tsx
import { compileAgent } from "@agentick/core/testing";

const { sections, tools, messages } = await compileAgent(MyAgent, {
  props: { mode: "helpful" },
  messages: [{ role: "user", content: "Hello" }],
});

expect(sections.get("instructions")).toContain("helpful");
expect(tools.map((t) => t.name)).toContain("search");

Test Runner

createTestRunner creates a mock ExecutionRunner with lifecycle call tracking:

tsx
import { createTestRunner } from "@agentick/core/testing";

const { runner, tracker } = createTestRunner();
const app = createApp(Agent, { model, runner });
const session = await app.session();
await session.send({ messages: [...] }).result;

expect(tracker.initCalls).toHaveLength(1);
expect(tracker.transformCompiledCalls).toHaveLength(1);

Intercept Tools

Replace tool execution with test responses:

tsx
// Static string results
const { runner } = createTestRunner({
  interceptTools: { execute: "sandbox result" },
});

// Dynamic function results
const { runner } = createTestRunner({
  interceptTools: {
    execute: (call) => ({
      id: call.id,
      toolUseId: call.id,
      name: call.name,
      success: true,
      content: [{ type: "text", text: `ran: ${call.input.code}` }],
    }),
  },
});

Tracker Fields

FieldTracks
initCallsSession IDs from onSessionInit
transformCompiledCallsTool names from transformCompiled
toolCallsTool names + intercepted flag
persistCallsSession IDs from onPersist
restoreCallsSession IDs from onRestore
destroyCallsSession IDs from onDestroy

Mock Factories

createMockSession

tsx
import { createMockSession } from "@agentick/core/testing";

const session = createMockSession({ executionOptions: { response: "Hello!" } });
const handle = await session.send({ messages: [] });
const result = await handle.result;

expect(result.response).toBe("Hello!");
expect(session._sendCalls).toHaveLength(1);

createMockApp

tsx
import { createMockApp } from "@agentick/core/testing";

const app = createMockApp();
const session = await app.session("test");
expect(app.has("test")).toBe(true);

createTestProcedure

tsx
import { createTestProcedure } from "@agentick/kernel/testing";

const mockSend = createTestProcedure({
  handler: (input) => ({ response: "mocked" }),
});

await mockSend({ messages: [] });
expect(mockSend._callCount).toBe(1);

mockSend.respondWith({ response: "custom" }); // One-shot
mockSend.setResponse({ response: "permanent" }); // Persistent

Testing Patterns

Test tool execution

tsx
test("agent uses search tool", async () => {
  const model = createTestAdapter();
  const { send, result } = renderAgent(SearchAgent, { model });

  model.respondWith(["Let me search", { tool: { name: "search", input: { q: "weather" } } }]);
  model.respondWith(["The weather is sunny!"]);

  await act(() => send("What's the weather?"));
  expect(result.current.lastAssistantMessage).toContain("sunny");
});

Test multi-turn conversations

tsx
test("agent remembers context", async () => {
  const model = createTestAdapter();
  const { send, result } = renderAgent(ChatAgent, { model });

  model.respondWith(["Nice to meet you, Alice!"]);
  await act(() => send("Hi, I'm Alice"));

  model.respondWith(["Of course, Alice!"]);
  await act(() => send("Remember my name?"));

  expect(result.current.lastAssistantMessage).toContain("Alice");
});

Test error handling

tsx
test("agent handles model errors", async () => {
  const model = createTestAdapter();
  const { send, result } = renderAgent(MyAgent, { model });

  model.setError(new Error("API rate limited"));
  await act(() => send("Hello"));

  expect(result.current.error).toBeDefined();
});

Released under the ISC License.