OpenParable
Skip to content
← Back to skills

ag2-testing

Testing & Quality

Test AG2 beta agents and tools without hitting a real LLM provider. Pass `TestConfig(...)` from `autogen.beta.testing` as the agent's config (or per-`ask`) to mock LLM responses, inject `ToolCallEvent`s to simulate tool execution, and assert success / error paths. Use when the user is writing pytest tests for an Agent or Tool.

View on GitHub ↗
QUICK START

How to use this skill

Bring this guide into your coding agent with a prompt tailored to the tool you use.

  1. Open your project in Codex.
  2. Copy and paste the prompt below.
  3. Review files and risks before approving any action.
Prompt to paste
Inspect the Agent Skill at https://github.com/ag2ai/build-with-ag2/blob/HEAD/.agents/skills/ag2-testing/SKILL.md and all supporting files. Report provenance, permissions, scripts, dependencies, and security risks. Propose an exact installation into .agents/skills/ag2-testing, but wait for my approval before writing files or running scripts.

Copying this prompt does not install or run the skill. Review third-party files before use.

Testing agents and tools

When to use

Writing tests for code that builds AG2 beta Agents, custom @tool functions, middleware, or response schemas — anywhere you don't want to make real LLM API calls.

60-second recipe — mock an LLM response

import pytest
from autogen.beta import Agent
from autogen.beta.testing import TestConfig

@pytest.mark.asyncio
async def test_mocked_response():
    agent = Agent("test_agent")
    reply = await agent.ask("Hi!", config=TestConfig("This is a mocked response."))
    assert reply.body == "This is a mocked response."

TestConfig(*responses) replaces the model client. Each positional arg is the mocked response for the next sequential turn — strings for text replies, ToolCallEvent for tool dispatches.

Simulate a successful tool call

Pass a ToolCallEvent first (the model "decides" to call the tool), then the final answer:

import pytest
from autogen.beta import Agent
from autogen.beta.events import ToolCallEvent
from autogen.beta.testing import TestConfig

@pytest.mark.asyncio
async def test_tool_success():
    def my_tool() -> str:
        return "tool execution result"

    agent = Agent("test_agent", tools=[my_tool])
    config = TestConfig(
        ToolCallEvent(name="my_tool"),
        "final result",
    )
    reply = await agent.ask("Please use my_tool", config=config)
    assert reply.body == "final result"

Test tool error paths

If a tool raises, the exception propagates to ask():

@pytest.mark.asyncio
async def test_tool_raises():
    def failing_tool() -> str:
        raise ValueError("Something went wrong")

    config = TestConfig(
        ToolCallEvent(name="failing_tool"),
        "result",
    )
    agent = Agent("test_agent", config=config, tools=[failing_tool])

    with pytest.raises(ValueError, match="Something went wrong"):
        await agent.ask("Hi!")

Tool not found

If the LLM calls a tool the agent doesn't have, the framework raises ToolNotFoundError:

from autogen.beta.exceptions import ToolNotFoundError

@pytest.mark.asyncio
async def test_tool_not_found():
    config = TestConfig(ToolCallEvent(name="unregistered_tool"))
    agent = Agent("test_agent", config=config)
    with pytest.raises(ToolNotFoundError, match="Tool `unregistered_tool` not found"):
        await agent.ask("Hi!")

Useful test patterns

Override Depends dependencies

def get_production_db():
    raise Exception("Do not call in tests!")

@tool
def read_data(db: Annotated[object, Depends(get_production_db)]) -> str:
    return "Data"

agent = Agent("test", tools=[read_data])
agent.dependency_provider.override(get_production_db, lambda: "mock_db")

Override Inject dependencies

Just pass dependencies={...} to agent.ask(...):

await agent.ask("Read", dependencies={"database_pool": fake_pool})

Capture stream events

from autogen.beta import MemoryStream
from autogen.beta.events import ToolCallEvent

stream = MemoryStream()
collected: list[ToolCallEvent] = []
stream.where(ToolCallEvent).subscribe(lambda e: collected.append(e))

await agent.ask("Test", stream=stream)
assert collected[0].name == "expected_tool"

Multi-turn mock

Each positional arg in TestConfig(...) corresponds to one model response. For a multi-turn test, supply enough responses for each turn the test exercises.

Going deeper

  • Source doc: website/docs/beta/testing.mdx.
  • Test markers / async config — repo pyproject.toml. Use @pytest.mark.asyncio (the project uses pytest-asyncio).
  • Streams (for asserting events): website/docs/beta/advanced/stream.mdx.

Common pitfalls

  • Forgetting @pytest.mark.asyncio — the test will skip or fail oddly.
  • Mismatched response count — TestConfig runs out of responses if the agent makes more LLM calls than you expect (e.g. tool error → another LLM call). Add more positional args or assert that the call sequence is what you intended.
  • Mocking the LLM but not the tool — your tool function still runs (and may hit real APIs / disk). Mock the tool if you're isolating LLM behaviour, or override its Depends to inject test doubles.
  • Asserting on reply.body when you set a response_schema — body is the raw text. Use await reply.content() for the validated value.
  • Sharing Agent instances across async tests — agents carry mutable state (variables, dependencies). Construct fresh agents per test for isolation.
  • Using real provider clients in CI — wrap the provider config with TestConfig per-test or via a fixture; never rely on OPENAI_API_KEY etc. being available in test environments.