Skip to main content

Mock a GraphQL API

proxymock mock answers your app's GraphQL requests from a recording, so the app runs with no API behind it: no account, no rate limit, no test data to keep alive, and the same answers every time. That makes an app that depends on a GraphQL API testable on a laptop, in CI and by an AI coding assistant.

Run the mock​

Record the API once, then start the mock the same way and run the app under it:

proxymock mock --in proxymock/recorded-<timestamp> -- ./your-app

If you recorded through a mapped port, map it again so the app reaches the mock on the same address:

proxymock mock --in proxymock/recorded-<timestamp> --map 18081=http://localhost:18080 -- ./your-app

The mock writes what it served to proxymock/results/mocked-<timestamp>, one file per request, tagged HIT when a recording answered it and MISS when none did. A miss is passed through to the real API, or, with --no-passthrough, answered with a 404, which is the setting to use when you want misses to be visible.

What a GraphQL mock matches on​

A request matches a recording when its method, host, path and query parameters match and its body means the same thing. For GraphQL, the body is compared as a parsed document, so:

Does not change the matchChanges the match
Whitespace, line breaks and comments in the queryThe operation and its name
The order arguments are written inWhich fields are selected, and in what order
The order of an input object's fieldsA variable's value
The order of keys inside variablesAn argument literal written into the query

Selection order counts because it is the order of the fields in the response, so two requests that differ in it are owed different answers.

When a request does not match​

The usual cause is a variable that changes on every run: a signed token, a request id, a generated email address. Two runs of the same client send the same document with different variables, so the second run misses every recording of the first.

The graphql-demo driver shows it. It sends seven operations, and its Session query carries a freshly signed JWT and a random request id on every run. Run it against a mock of its own recording, with misses visible:

proxymock mock --in proxymock/recorded-<timestamp> --map 18081=http://localhost:18080 \
--no-passthrough -- ./graphql-demo -drive -target http://localhost:18081

The first request, Session, misses, and the driver stops.

Open the workspace with proxymock web and select Mocks. Select a field to open the request that missed:

The Mocks view in proxymock web after the first mock run of the graphql-demo driver: 0% of 1 request matched, Apply all recommended, and one To fix group, POST /graphql, recommending two fields, http.req.bodyBase64.variables.requestId and http.req.bodyBase64.variables.token, each with Mask and Replay recorded buttons. Below it the Session request is open on Request, Body, showing the operation query Session and the requestId and token variables with their values.

The Mocks view finds the fields that drifted between the request and its nearest recording and recommends a fix for each. For Session it recommends masking variables.token and variables.requestId. It never recommends masking the query or operationName, because those say which operation the request is, and masking them would let any operation match any other. An accepted fix is a responder chain in the workspace's tuning blueprint, scoped to the GraphQL endpoint:

{
"filters": { "filters": [ { "include": true, "operator": "REGEX", "optUrl": "^/graphql$" } ] },
"extractor": { "type": "req_body" },
"transforms": [
{ "type": "json_path", "config": { "path": "variables.token" } },
{ "type": "constant", "config": { "new": "*" } }
]
}

The chain runs on both sides of the match, the recordings and each incoming request, so the token no longer decides it. With those two fixes the driver gets past Session and stops at CreateUser, whose variables.email is generated per run. One more accepted fix, and a third run completes the driver's whole pass with every request a HIT, no API running.

Accept recommendations wherever is convenient:

  • In proxymock web, on the Mocks view, choose Mask or Replay recorded for each field, or Apply all recommended.
  • From an AI coding assistant, with the mocks tool of the proxymock MCP server, or let the assistant run the whole loop: Improve Mock Match Rate with AI.

Clients that add __typename​

Apollo and other clients add __typename to selection sets. It changes the document without changing what the request asks for, so a recording made with one client can miss requests from another. Strip it from both sides with a responder chain:

req_body() → graphql_delete(field="__typename")

See GraphQL recipes for this and other chains, and Create and Verify Blueprints for where they live in a workspace.

Generated mocks​

A mock generated from a schema matches the generated operation exactly: its selection and its variables. A client that selects different fields of the same root field gets no match, because it is owed a different response. Record the client instead, or edit the generated request to the document the client sends.