Mocking
Mocking is the technique of replacing real dependencies with controlled substitutes during tests. For AI agent testing, mocking is essential: it makes tests fast, deterministic, and free by replacing real LLM API calls with scripted responses. Well-designed mocks let you test complex agent scenarios — error recovery, multi-turn reasoning, tool failure handling — without spending money on API calls or waiting for real network responses.
Why Mocking Matters
Without mocking, unit tests become integration tests: slow, expensive, and unpredictable. A unit test that takes 10 seconds because it calls a real LLM API will not get run before commits. A unit test that completes in 20ms will. The economics of testing reward fast tests; mocking is how you make agent tests fast.
Mocking also enables testing failure scenarios that are impossible or unreliable with real dependencies. You can script the LLM to return a malformed JSON response on the third call, verify that the agent handles the parse error gracefully, and confirm that it recovers correctly — something that would be difficult to trigger with a real LLM without manipulating prompts carefully.
MockLLMProvider
The primary mocking tool for OpenClaw agent tests is MockLLMProvider. It accepts a list of responses that will be returned in sequence:
from openclaw.testing import MockLLMProvider
# Script a sequence of responses
mock = MockLLMProvider(responses=[
{"type": "tool_call", "tool": "read_file", "args": {"path": "/data/report.txt"}},
{"type": "text", "content": "Based on the report, the key findings are..."},
])
# Access call history for assertions
assert mock.call_count == 2
assert mock.calls[0]["messages"][-1]["role"] == "user"
assert "read_file" in mock.calls[0]["response"]["tool"]The mock supports all response types that real providers return: text completions, tool calls (single and parallel), streaming chunks, error responses, and context-window-exceeded errors. Use error responses to test agent recovery behavior.
Mocking External Tools
When testing agent logic that uses tools, mock the tool implementations to return controlled data. Use the @patch_tool decorator to replace a tool's implementation for the duration of a test:
from openclaw.testing import patch_tool
@patch_tool("web_search", return_value={"results": [
{"title": "Python asyncio", "url": "https://docs.python.org/asyncio", "snippet": "..."}
]})
def test_agent_uses_search_results(mock_search):
result = run_agent("find Python asyncio docs")
mock_search.assert_called_once()
assert "asyncio" in result.outputMocking File and Network Access
Tools that access the file system or make HTTP requests should be tested with proper mocking. Use tmp_path (a built-in pytest fixture) for file system tests, and responses or httpx.MockTransport for HTTP-using tools. This keeps tests hermetic — they don't depend on real files or real network connectivity, making them safe to run in any environment.
Context Injection Patterns
Some agent behaviors are controlled by context injected into the system prompt (user name, current date, available plugins). Test these by constructing the agent with a specific context overrides dictionary rather than mocking at a lower level. This keeps tests closer to production behavior while still being deterministic about the inputs that matter for the test.
Verifying Mock Calls
A complete mock-based test verifies three things: that the code ran without exceptions (the happy path worked), that the mock was called with the expected arguments (the code asked for the right thing), and that the output reflects the mocked response (the code used what it received correctly). All three assertions together give high confidence in the tested behavior — a test missing any one of them has a gap in its coverage.
Mock Design Principles
Good mocks are minimal and specific. A mock that returns the simplest possible response that satisfies the test's scenario is better than a mock that returns a rich, realistic response — simpler mocks are easier to understand and maintain. Save detailed, realistic mock responses for the few tests that actually need to verify how the agent handles rich tool results.
Avoid over-specifying mocks. If your test asserts that a function was called, but doesn't care about which arguments it was called with, use a flexible matcher rather than an exact argument match. Over-specified mocks create brittle tests that fail when unrelated implementation details change — the mock becomes a constraint on the implementation rather than a contract for the interface.
Recording Real Responses as Mock Data
The VCR (video cassette recorder) pattern is a powerful technique for creating realistic mocks from real API responses. Run your agent once against a real LLM with recording enabled, capturing the exact request-response pairs. Future test runs replay the recording, getting realistic responses without making real API calls.
import pytest
from openclaw.testing import vcr
@vcr.use_cassette("tests/fixtures/cassettes/research_task.yaml")
@pytest.mark.asyncio
async def test_research_agent_recorded():
# This test replays a real agent run captured from a prior recording
agent = Agent.from_config("tests/fixtures/research_agent.yaml")
result = await agent.run_async("Research the history of Python")
assert result.status == "completed"
assert len(result.output) > 200Recorded cassettes are committed to the repository. Regenerate them when the agent's behavior intentionally changes — run once with OPENCLAW_VCR_RECORD=true, review the changes in the cassette file (which is human-readable YAML), and commit. The diff in cassette files documents exactly how the agent's request patterns changed.
Mocking Tool Failures for Recovery Testing
One of the most valuable uses of mocking is testing how the agent handles tool failures. Script the mock to return errors — network timeouts, permission denied errors, malformed responses — and verify that the agent handles them gracefully rather than crashing or producing nonsense output. Agents that handle tool failures well are dramatically more reliable in production where real tools fail regularly.
from openclaw.testing import patch_tool, ToolError
@patch_tool("web_search", side_effect=ToolError("Connection timeout"))
def test_agent_recovers_from_search_failure(mock_search):
result = run_agent("Find Python tutorials")
# Agent should still complete — it might say it couldn't search
assert result.status == "completed"
assert "timeout" in result.output.lower() or "unable" in result.output.lower()