SDKs & Developer Experience

Mocking Claude API Calls in Unit Tests

Your CI pipeline should not need an API key, a network connection, or a per-run model bill. Mocking makes Claude-dependent code testable like any other code.

Claude 3P 101 · Updated July 2026 · Unofficial guide

Code that calls Claude has two kinds of behavior worth testing, and only one of them needs a model. Whether the model gives good answers is an evaluation problem — run it deliberately, with real calls and human or automated grading. Whether your plumbing is correct — the loop terminates, tool results are injected in the right shape, errors are handled, the right things are logged — is an ordinary unit-testing problem, and real API calls make it slow, flaky, and expensive. Unit tests should mock; this article covers the three standard levels at which to do it.

Level 1: monkey-patching the SDK call

The simplest approach replaces the SDK method your code calls with a stub returning a canned response object. In Python with pytest:

def test_loop_stops_without_tool_calls(monkeypatch, client):
    def fake_create(**kwargs):
        return make_fake_message(  # your test factory
            content=[make_text_block("All done.")])
    monkeypatch.setattr(client.messages, "create", fake_create)

    result = run_agent(client, "do the thing")
    assert result.final_text == "All done."

Build one small factory for fake messages (content blocks, stop reason, usage) and reuse it everywhere. For tool-loop tests, have the stub return a tool_use-bearing message on the first call and a plain answer on the second — that single fixture pattern exercises dispatch, result injection, and termination (see the tool loop). The same idea works in every SDK language; TypeScript teams typically use their test framework's module mocking, and the TypeScript SDK explicitly supports Jest 28+ in the node environment.

The weakness: patching create bypasses the SDK's own serialization, so a malformed request body would sail through. That is what level 2 fixes.

Level 2: intercepting HTTP

HTTP-intercept libraries (respx or responses in Python, nock in Node) stub the network layer instead of the method, so the real SDK code runs — building the request, setting headers, parsing your fake JSON response into real SDK types. Your test asserts on the actual outgoing request: correct endpoint, correct model ID, tool definitions serialized as intended. This catches a class of bug that method-level mocks cannot, at the cost of writing response JSON in the API's wire shape. Record it once from a real call (see VCR cassettes) rather than hand-crafting it from memory.

Level 3: a fake server via base-URL override

For integration-style tests — or polyglot systems where several services call Claude — run a local fake server and point the client at it. The Anthropic tooling supports overriding the API host (the ant CLI, for example, honors an ANTHROPIC_BASE_URL environment variable or --base-url flag; check your SDK's README for its equivalent option). A ten-line fake server that replays fixture responses gives you language-agnostic, process-level isolation.

Credential hygiene in the test environment

Two documented behaviors bite test suites regularly:

The SDK auto-reads ANTHROPIC_API_KEY. Client classes pick the key up from the environment automatically. That is convenient in production and dangerous in CI: a leaked real key in the runner environment means an "accidentally unmocked" test quietly makes real, billed calls. Set a dummy value in test config, and make an unexpected outbound HTTP call a test failure (most intercept libraries can enforce this).

Empty is not unset. In the documented credential-resolution order, an environment variable set to an empty string still occupies its precedence slot — ANTHROPIC_API_KEY="" selects the API-key path with an empty key rather than falling through to profiles or federation. When tests manipulate credentials, unset variables; don't blank them.

Rule of thumb: a unit test that can spend money is a bug. CI should run with no real Claude credentials present, on any platform — the same discipline applies to AWS, Google Cloud, and Azure credentials when you use the third-party clients like AnthropicBedrockMantle or AnthropicVertex.

What not to mock

Keep a small, separately triggered suite of live smoke tests — real calls with a scoped key against the cheapest suitable model — to catch what mocks never will: authentication changes, serialization drift, deprecations. Run it on a schedule or before releases, not on every commit.

Where to go next

Recording and replaying API responses automates fixture creation for level 2 and 3. For the code under test, start with the Python tool loop.

Sources