
Ag2 Testing
- 33 installs
- 8 repo stars
- Updated July 27, 2026
- ag2ai/ag2-skills
ag2-testing is a Claude Code skill that mocks AG2 beta agents and tools with TestConfig so pytest tests run without a real LLM provider.
About
This skill shows how to write pytest tests for AG2 beta agents and tools without making real LLM API calls. A developer passes TestConfig(...) as the agent's config to mock LLM responses and injects ToolCallEvent objects to simulate tool execution, then asserts success and error paths. It documents overriding Depends/Inject dependencies, capturing stream events, and the per-ask() cursor behaviour of the mocked response list.
- Test AG2 beta agents and tools with pytest without hitting a real LLM provider
- TestConfig mocks LLM responses and ToolCallEvent simulates tool execution
- Covers success paths, error paths, ToolNotFoundError, dependency overrides, and stream-event capture
Ag2 Testing by the numbers
- 33 all-time installs (skills.sh)
- Ranked #1,333 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 1, 2026 (Skillselion catalog sync)
ag2-testing capabilities & compatibility
Free; the whole point is that tests run without hitting a real LLM provider, so no API key is needed.
- Capabilities
- agent testing · llm mocking · tool mocking · unit testing
- Use cases
- testing
- Pricing
- Free
What ag2-testing says it does
Test AG2 beta agents and tools without hitting a real LLM provider.
`TestConfig(*responses)` replaces the model client.
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 33 |
|---|---|
| repo stars | ★ 8 |
| Last updated | July 27, 2026 |
| Repository | ag2ai/ag2-skills ↗ |
What it does
Write pytest tests that mock an AG2 beta agent's LLM responses and tool calls to assert success and error paths.
Who is it for?
Developers writing pytest tests for AG2 beta agents, custom @tool functions, middleware, or response schemas.
Skip if: Runtime agent building or non-test agent behavior.
When should I use this skill?
The user is writing pytest tests for an Agent or Tool and wants to avoid real LLM calls.
What you get
Tests deterministically mock LLM replies and tool calls and assert both success and error paths.
- pytest tests mocking AG2 agent LLM and tool behavior
By the numbers
- covers 4 tool test scenarios: success, raises, not-found, and dependency override
Files
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 LLM call within an `ask()` — strings for text replies, ToolCallEvent for tool dispatches. (The cursor is per-ask(); see "Multi-turn mock" below for what that means across multiple turns.)
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 — the response list is per-ask(), not per-conversation
TestConfig(...)'s response list is consumed within a single `ask()`, across that round's repeated LLM calls — that's why TestConfig(ToolCallEvent("my_tool"), "final result") works for a tool-using turn (the LLM emits the tool call, the tool runs, the LLM is called again and gets "final result"). Internally, TestConfig.create() hands back a fresh client whose iterator starts at responses[0], and that's done once per `ask()` — so every new ask() (a reply.ask(...) chain, or each turn the network adapters / an auto-replying agent drive) restarts the cursor at the first response. Listing more responses does not let you say "conversational turn 2 differs from turn 1".
For variation across multiple ask() calls, either:
- pass a fresh
TestConfig(...)per turn via the per-ask()config=override (await agent.ask("…", config=TestConfig("turn-2 reply"))), or - mock the model with a
ToolCallEventand put the per-turn logic in a stateful tool — a closure or class instance that tracks how many times it's been called and returns accordingly. (Useful when something other than your test code drives the turn loop — e.g. aworkflow/discussionchannel — so you can't inject a per-turnconfig=.)
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 —
TestConfigruns out of responses if the agent makes more LLM calls than you expect within one `ask()` (e.g. tool error → another LLM call);StopIterationpropagates. Add more positional args or assert the call sequence. (The cursor is per-ask(), so the count you need is "LLM calls in one round", not "turns in the conversation" — see Multi-turn mock above.) - 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
Dependsto inject test doubles. - Asserting on `reply.body` when you set a `response_schema` —
bodyis the raw text. Useawait 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
TestConfigper-test or via a fixture; never rely onOPENAI_API_KEYetc. being available in test environments.
Related skills
FAQ
How do I mock an LLM response in AG2 tests?
Pass TestConfig('mocked response') as the agent's config (or per-ask); reply.body returns the mocked string with no real API call.
How do I simulate a tool call?
Pass a ToolCallEvent(name='my_tool') first so the model 'decides' to call the tool, then the final answer string in TestConfig(...).