Now liveThe Skillselion MCP - thousands of ranked skills, loaded into your agent mid-task. No install.Get it →
ag2ai avatar

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)
At a glance

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
From the docs

What ag2-testing says it does

Test AG2 beta agents and tools without hitting a real LLM provider.
SKILL.md
`TestConfig(*responses)` replaces the model client.
SKILL.md
npx skills add https://github.com/ag2ai/ag2-skills --skill ag2-testing

Add your badge

Show developers this skill is listed on Skillselion. Paste this into your README.

Listed on Skillselion
Installs33
repo stars8
Last updatedJuly 27, 2026
Repositoryag2ai/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

SKILL.mdMarkdownGitHub ↗

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 ToolCallEvent and 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. a workflow / discussion channel — so you can't inject a per-turn config=.)

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 countTestConfig runs out of responses if the agent makes more LLM calls than you expect within one `ask()` (e.g. tool error → another LLM call); StopIteration propagates. 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 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.

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(...).

Testing & QAtestingbackend

This week in AI coding

Five minutes, every Monday - the tools, releases and tactics for developers.

unsubscribe anytime.