PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/Mocking Tools in Your Agent Test Harness
AI Harness

Mocking Tools in Your Agent Test Harness

Mock tools agent testing keeps your suite fast, safe, and free of real side effects. Learn to key mocks to arguments, record real responses, and test failures.

August 31, 2026·8 min read
ShareShare
⚡Featured Prompt— copy and use right now
def mock_tool(responses):
    def _tool(**kwargs):
        return responses.get(frozenset(kwargs.items()), "default response")
    return _tool

charge = mock_tool({
    frozenset({"amount": 100, "card": "test"}.items()): "SUCCESS: charged $1.00",
})

Picture this: you're a backend engineer, and your agent's test suite just charged a real credit card. Not much — a test card, a few cents — but the test called the live payment tool because nobody mocked it, and now your CI run has side effects in a payment processor's logs. That's the moment mock tools agent testing stops being optional. Tests that call real tools are slow, flaky, expensive, and occasionally dangerous. Mocking the tools fixes all four, and doing it well is more subtle than returning a hardcoded string.

What Does It Mean to Mock a Tool?

Mocking a tool means replacing the real function with a stand-in that returns controlled responses, so your test exercises the agent's logic without touching the outside world. The agent thinks it called

charge_card
; really it called a mock that returned a canned success without moving any money.

The value is straightforward once you've been burned by the alternative. A mocked tool is fast because there's no network call, deterministic because it returns the same thing every time, free because it hits no paid API, and safe because it has no real side effects. Every one of those matters for tests you run on every commit.

⚡ Pro tip: Mock at the tool boundary, never inside the agent's reasoning. The whole point is to test the real agent logic against controlled tool behavior — so replace the tool function, and leave everything the agent does with that tool's response untouched. Mocking any deeper means you're no longer testing the agent; you're testing a hollowed-out shell of it.

Here's the simplest version:

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], ,[object Object],(,[object Object],):
        ,[object Object], responses.get(,[object Object],(kwargs.items()), ,[object Object],)
    ,[object Object], _tool

charge = mock_tool({
    ,[object Object],({,[object Object],: ,[object Object],, ,[object Object],: ,[object Object],}.items()): ,[object Object],,
})

What this does: it builds a mock that returns a specific response for a specific set of arguments, falling back to a default otherwise. The agent calls it exactly as it would the real tool, but the response is one you chose. This keys the response to the arguments, so different inputs can return different canned outputs.

Why Mocking Is More Than Hardcoding

The naive mock returns one fixed string, and that's where most people stop. But a fixed string quickly stops resembling the real tool, and a test against an unrealistic mock gives you false confidence. Three refinements make mocks earn their keep.

Key responses to arguments. Real tools return different things for different inputs. A mock that returns "SUCCESS" no matter what won't catch the agent passing wrong arguments. Keying the response to the arguments — as above — lets the mock reward correct calls and expose incorrect ones.

Mock the error paths, not just the happy path. The most valuable tests are the ones where a tool fails, because that's where agents behave unpredictably. A mock that can raise a timeout or return an error lets you test whether the agent recovers — which you can't do reliably with a real tool that mostly succeeds.

Keep mocks realistic by recording real responses. The best mocks aren't hand-written; they're recorded. Call the real tool once, capture its actual response, and replay that as your mock. Recorded fixtures stay faithful to the real tool's shape — its exact fields, its quirks — in a way hand-written mocks drift away from over time.

The recording approach also solves a problem hand-written mocks can't: capturing the weird responses you'd never think to write. Real APIs return null where you expected a value, empty arrays, unexpected error codes, fields that are sometimes strings and sometimes numbers. A hand-written mock encodes your tidy mental model of the tool; a recorded fixture encodes the tool's actual messy behavior. Since agents break on exactly those messy edges, mocks built from real recordings test the cases most likely to fail — the ones your imagination would have smoothed over.

⚠️ Common mistake: Writing mocks by hand from memory of what a tool returns. Your memory of the API's response shape is wrong in small ways — a field name, a nesting level, a string where you thought there was a number — and your mock encodes those errors. Then your agent passes the test against the wrong shape and fails against the real tool. Record real responses once and replay them; don't reconstruct them from memory.

The Mock Tools Agent Testing Pattern

The pattern that scales is a mock that can return recorded responses and injected failures, selected per test. Here's a fuller version:

hljs python
[object Object], ,[object Object],:
    ,[object Object], ,[object Object],(,[object Object],):
        ,[object Object],.fixtures = fixtures      ,[object Object],
        ,[object Object],.calls = []               ,[object Object],

    ,[object Object], ,[object Object],(,[object Object],):
        ,[object Object],.calls.append(kwargs)
        key = kwargs.get(,[object Object],, ,[object Object],)
        response = ,[object Object],.fixtures[key]
        ,[object Object], ,[object Object],(response, Exception):
            ,[object Object], response            ,[object Object],
        ,[object Object], response

What this does: it returns recorded responses keyed by scenario, raises an exception when the fixture is an error (so you can test recovery), and records every call the agent made so your test can assert on them afterward. That

calls
list is quietly the most useful part — it lets you check not just the final result but exactly how the agent used the tool.

Now a test of the error path becomes easy:

hljs python
[object Object], ,[object Object],():
    mock = ToolMock({,[object Object],: TimeoutError(,[object Object],), ,[object Object],: ,[object Object],})
    agent = Agent(tools={,[object Object],: mock})
    result = agent.run(,[object Object],)
    ,[object Object], result.success
    ,[object Object], ,[object Object],(mock.calls) >= ,[object Object],      ,[object Object],

What this does: it makes the tool time out once, then succeed, and asserts that the agent retried and ultimately succeeded — a behavior that's nearly impossible to test against a real tool you can't force to fail on command. Mocking is what makes failure testable.

The

calls
list also lets you write a whole class of tests about tool usage discipline that final-output checks miss entirely. Did the agent call an expensive tool more times than necessary? Did it call a destructive tool it shouldn't have touched at all? Did it pass a required argument? These are questions about the trajectory, and the recorded call list is where the answers live:

hljs python
[object Object], ,[object Object],():
    mock = ToolMock({,[object Object],: ,[object Object],})
    agent = Agent(tools={,[object Object],: mock})
    agent.run(,[object Object],)
    ,[object Object], ,[object Object],(mock.calls) <= ,[object Object],      ,[object Object],

What this does: it asserts the agent didn't call an expensive search tool more than twice for a task that needs one or two lookups — catching a cost regression where a prompt change made the agent search wastefully. Without the call record, this inefficiency would pass every output-based test while quietly inflating the bill.

⚡ Pro tip: Test on argument correctness, not just call counts. Assert that the agent called

charge_card
with the right amount, not merely that it called it. A surprising number of agent bugs are right-tool-wrong-argument, and only an assertion that inspects the recorded arguments will catch them.

⚡ Pro tip: Distinguish a mock from a fake. A mock returns canned responses; a fake is a real, working, in-memory implementation — like a dictionary standing in for a database. For simple tools, mocks are enough. For a tool with complex stateful behavior the agent interacts with over many calls, a fake often produces more realistic tests, because it actually behaves like the thing instead of replaying a script.

Real Scenarios Where Mocking Pays Off

The pattern proves itself across very different agents:

  • A payments engineer mocks the charge tool with recorded success and decline responses, testing that the agent handles a declined card gracefully — without ever touching a real processor.
  • A DevOps engineer mocks a deployment API to inject a mid-deploy failure, verifying the agent rolls back correctly, which would be reckless to test against real infrastructure.
  • A data engineer uses a fake in-memory table instead of a mocked query tool, so the agent's multi-step read-transform-write sequence runs against realistic stateful behavior without a real warehouse.

In each case, mocking or faking the tool turns a test that was slow and risky into one that's fast and safe — and makes failure scenarios testable at all.

Common Mistakes

⚠️ Common mistake: Only ever mocking the happy path. If every mock returns success, your tests prove the agent works when nothing goes wrong — which is the least interesting and least likely case in production. Agents earn their keep by handling failure, and you can only test failure handling by mocking tools that fail. For every happy-path mock, write at least one that returns an error, a timeout, or malformed data.

A few more traps:

  • Mocks that drift from reality. Re-record your fixtures periodically against the real tool, or they slowly stop matching the API and your tests pass while production breaks.
  • Over-mocking. If you mock so much that the test barely exercises real agent logic, it tests your mocks, not your agent. Mock the boundaries, not the middle.
  • Ignoring the call record. The sequence of calls the agent made is signal. Assert on it — wrong tool, wrong order, and wrong arguments all hide there.
  • Forgetting to reset mock state between tests. A
    ToolMock
    that accumulates its
    calls
    list across tests will leak one test's calls into the next, producing baffling failures. Build a fresh mock per test, or clear it in setup, so every test starts from a clean slate.

Conclusion

Mock tools agent testing turns a slow, risky, expensive test suite into a fast and safe one, and it's the only practical way to test how your agent handles tools that fail. Key your mocks to arguments, record real responses instead of inventing them, and always test the error paths — because that's where agents actually break.

The recorded fixtures and failure scenarios you build are reusable across every version of your agent, and they encode real knowledge about how your tools behave. Keeping those mock fixtures and the scenarios they cover organized in a library like PromptABCD — tagged by the tool and the failure they simulate — means your next agent starts with a realistic test harness instead of a pile of hand-written mocks that drift from the truth. Record once, test forever.

mock tools agent testingmockingtestingai agentsfixturestest harness

Continue Reading

How to Run Repeatable Agent Tests Without the Flakes
AI Harness

How to Run Repeatable Agent Tests Without the Flakes

Repeatable agent testing means pinning the three sources of nondeterminism. A case study on going from 47% flaky CI to reliably green by freezing all three.

August 31, 2026·8 min read
The SWE-bench Harness Explained for Agent Builders
AI Harness

The SWE-bench Harness Explained for Agent Builders

The swe-bench harness fails logically correct patches when the environment is wrong. Learn how it grades, what FAIL_TO_PASS means, and how to run it yourself.

August 28, 2026·8 min read
Building an Evaluation Harness for Your Agent
AI Harness

Building an Evaluation Harness for Your Agent

The best way to build agent eval harness infrastructure isn't LLM-as-judge. Learn to design verifiable tasks and programmatic graders you can actually trust.

August 28, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousHow to Run Repeatable Agent Tests Without the Flakes
Share this post:
ShareShare