Claude API Mock Server for Testing: A Dev Guide
Why you need a Claude API mock server for testing
Calling the real Claude API in your test suite is slow, costs money, and makes tests flaky whenever the model's output varies slightly between runs. A Claude API mock server solves this by returning deterministic, pre-defined responses that look exactly like the real API, so your tests run fast, offline, and consistently in CI.
The goal isn't to simulate model intelligence — it's to simulate the transport layer: request shapes, response shapes, streaming chunks, tool-use payloads, and error conditions. If your application code can correctly parse and handle a mocked response, it will handle the real one too. This article walks through the practical options, from a hand-rolled Express server to interception libraries, plus the scenarios your mocks actually need to cover.
What a good mock needs to cover
A mock that only returns a single happy-path JSON blob will miss most of the bugs that show up in production. Structure your mocks around these cases:
Non-streaming messages
The baseline case: a POST to a messages endpoint returns a complete response object with content, stop_reason, and usage fields. This is the easiest to mock and should be your first fixture.
Streaming responses
Claude's streaming format sends a sequence of server-sent events (message_start, content_block_delta, message_delta, message_stop). Your mock server needs to replay these as actual SSE chunks over time, not just return one blob — otherwise you never catch bugs in your stream-parsing code. See /docs/streaming for the event shapes if you're building against SubToAPI's format.
Tool use calls
If your app uses function calling, mock at least one response where stop_reason is tool_use and the content includes a tool call block with arguments. Your test suite should assert that your code correctly extracts the tool name and input, executes it, and sends the result back in the next turn. Reference /docs/tools for the request/response structure.
Error and rate-limit conditions
Mock 429s, 500s, and malformed-input 400s. Production incidents are far more often caused by unhandled error branches than by the happy path, so this is the highest-value category to test.
Building a minimal mock server
A small Express server gives you full control over status codes, headers, and streaming timing. Here's a minimal example that handles a non-streaming and a streaming case:
const express = require('express');
const app = express();
app.use(express.json());
app.post('/v1/messages', (req, res) => {
if (req.body.stream) {
res.setHeader('Content-Type', 'text/event-stream');
const chunks = [
{ type: 'message_start', message: { id: 'msg_mock_1' } },
{ type: 'content_block_delta', delta: { text: 'Hello' } },
{ type: 'content_block_delta', delta: { text: ' world' } },
{ type: 'message_stop' }
];
let i = 0;
const interval = setInterval(() => {
if (i >= chunks.length) {
clearInterval(interval);
return res.end();
}
res.write(`event: ${chunks[i].type}\ndata: ${JSON.stringify(chunks[i])}\n\n`);
i++;
}, 50);
return;
}
res.json({
id: 'msg_mock_2',
content: [{ type: 'text', text: 'Mock response' }],
stop_reason: 'end_turn',
usage: { input_tokens: 12, output_tokens: 4 }
});
});
app.listen(4010, () => console.log('Mock server running on :4010'));
Point your app's base URL at http://localhost:4010 during tests instead of the real API host. This approach is worth the setup cost if you need fine-grained control over timing (e.g. testing timeout handling) or if multiple services in your stack need the same mock.
Using interception libraries instead of a server
If you don't want to run a separate process, intercept HTTP calls directly in your test runner with a library like nock (Node) or msw (works in both Node and browser tests):
const nock = require('nock');
nock('https://api.subtoapi.app')
.post('/v1/messages')
.reply(200, {
id: 'msg_mock_3',
content: [{ type: 'text', text: 'Intercepted response' }],
stop_reason: 'end_turn',
usage: { input_tokens: 10, output_tokens: 5 }
});
This is faster to set up for unit tests and avoids managing a second server's lifecycle, but it's less realistic for testing actual network behavior like timeouts or connection resets. Use interception for unit tests and a real mock server for integration or end-to-end tests.
Recording real responses for fixtures
Instead of hand-writing every mock payload, record real API calls once and replay them. Libraries like nock support "recording mode," where you make the real call once, save the response to a JSON file, then replay it on every subsequent test run:
nock.recorder.rec({ output_objects: true });
// make a real call here once, then save nock.recorder.play() to a fixture file
This gives you realistic payload shapes without guessing field names, and keeps your fixtures in sync with the actual API contract when you periodically re-record them.
Where this fits with SubToAPI
If your app talks to Claude through SubToAPI — using sub_live_ keys against https://api.subtoapi.app/v1/messages — the same mocking strategies apply directly, since the request and response shapes follow the standard messages format documented at /docs/messages. During development you can run against the real API with a free trial key from /signup, then switch your test environment to a local mock once you've confirmed the integration works. Keeping production calls in staging and mocks in CI is the standard split: it avoids burning quota on every pull request and keeps your /pricing tier's usage focused on real traffic rather than test runs.
Questions
Does mocking the Claude API in tests cost money? No. A mock server or interception library runs entirely locally and makes no real network calls to Claude or any provider, so it has zero cost and no rate-limit impact.
Should I mock streaming responses differently from regular ones? Yes. Streaming needs to replay a sequence of SSE events over time rather than returning one JSON object, otherwise you won't catch bugs in code that parses partial chunks.
Can I use the same mock server for multiple projects? Yes — run it as a shared local service or Docker container with a fixed port, and point any project's test environment base URL at it during CI.