Anthropic

How to test Claude API code

Apps built on Claude often do more than one call: they stream responses into a UI, run tool use loops, or chain several prompts together. This guide shows how to test that code against real Claude responses — deterministically, offline, and without spending tokens on every run.

Setup

The official Anthropic SDKs for Python and TypeScript read the ANTHROPIC_BASE_URL environment variable. retake sets it for the command it runs, so every Messages API call goes through the proxy — no code changes.

curl -fsSL https://zecrari.github.io/retake/install.sh | sh

ANTHROPIC_API_KEY=sk-ant-... retake record -- pytest   # once
retake replay -- pytest                                # every run after

Python example

import anthropic, re, pytest

@pytest.fixture
def client(request):
    session = re.sub(r"[^A-Za-z0-9._/-]", "_", request.node.nodeid.replace("::", "/"))
    return anthropic.Anthropic(default_headers={"x-retake-session": session})

def summarize(client, text):
    message = client.messages.create(
        model="claude-sonnet-5-5",
        max_tokens=300,
        system="Summarize the support ticket in one sentence.",
        messages=[{"role": "user", "content": text}],
    )
    return message.content[0].text

def test_summary_mentions_the_product(client):
    summary = summarize(client, "My Pro plan invoice shows the wrong VAT number.")
    assert "invoice" in summary.lower()

Each test records into its own cassette, such as cassettes/tests/test_support.py/test_summary_mentions_the_product.json.

Tool use loops

A tool use loop sends Claude's tool_use blocks back as tool_result blocks until stop_reason isn't tool_use. On replay:

  1. The first response is the recorded one, including the same tool_use block and ID.
  2. Your code runs the same tool with the same input. If the tool is deterministic in tests, it returns the same result.
  3. The follow-up request is identical to the recorded one, so it matches — and the loop replays to the end.

If a tool's output changes, the follow-up request changes, and retake reports exactly which step diverged with a diff. See How to test AI agents deterministically for making tools deterministic and choosing what to assert.

Streaming

With stream=True or client.messages.stream(...), the API sends server-sent events: message_start, content_block_start, content_block_delta, content_block_stop, message_delta and message_stop. retake stores each event whole and replays the stream byte-for-byte, so the SDK's stream helpers and your UI code see exactly the live sequence.

Replays are instant by default. To test timeouts or progressive rendering with realistic pacing, record with --record-timing and replay with --timing realtime.

Thinking, prompt caching and other features

  • Extended thinking. Thinking blocks are part of the response and are replayed as recorded. If your code sends them back in the next turn, that request matches the recording too.
  • Prompt caching. cache_control markers are part of the request body, so they're matched like everything else. Replayed responses include the usage numbers from the recording.
  • Message Batches and Files. These are ordinary HTTP endpoints and are recorded too, though polling loops make cassettes long; consider fixtures for those.

Tips

  • Pin the model name you test with, and re-record deliberately when you upgrade.
  • The x-api-key header is never written to cassettes, and key-like strings in bodies are redacted. Run retake check before committing.
  • In CI, retake replay sets a placeholder ANTHROPIC_API_KEY so the SDK starts without a real key.
  • Frameworks built on the official SDK usually honor ANTHROPIC_BASE_URL too. If yours doesn't, pass the base URL explicitly from the environment.