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:
- The first response is the recorded one, including the same
tool_useblock and ID. - Your code runs the same tool with the same input. If the tool is deterministic in tests, it returns the same result.
- 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_controlmarkers 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-keyheader is never written to cassettes, and key-like strings in bodies are redacted. Runretake checkbefore committing. - In CI,
retake replaysets a placeholderANTHROPIC_API_KEYso the SDK starts without a real key. - Frameworks built on the official SDK usually honor
ANTHROPIC_BASE_URLtoo. If yours doesn't, pass the base URL explicitly from the environment.