How to Test GraphQL APIs: Techniques & Tools (2026)
Quick answer
Testing a GraphQL API means sending a POST with a JSON query and variables to a single endpoint (typically /graphql), then checking the response in two places: the data field for correct results, and the errors array for resolver-level failures — GraphQL almost always returns HTTP 200 even when a query fails, so a status-code-only assertion misses real errors entirely. Because it's just a POST with JSON, every framework already covered here (pytest, REST Assured, Playwright, k6, Postman) tests GraphQL with no new library.
Reviewed by Smeet Gohel
Testing a GraphQL API means sending a POST request with a JSON body — a query string and variables object — to a single endpoint, then checking the response in two places instead of one: the data field for correct results, and the errors array for resolver-level failures. That second part is the detail most REST-testing habits miss, since GraphQL returns HTTP 200 for almost every syntactically valid request, error or not.
This guide covers what's actually different about GraphQL testing, working code in the frameworks already covered in this series, and the tools built specifically for GraphQL.
Table of Contents
- What's Different About Testing GraphQL
- Testing GraphQL with the Frameworks You Already Have
- Testing Mutations
- The errors Array: Testing Failure Cases
- Query Depth and Complexity Limiting
- Dedicated GraphQL Tools
- Common Pitfalls in GraphQL Testing
- FAQ
What's Different About Testing GraphQL
Three structural differences from REST drive everything else in this guide:
- One endpoint, not many. Every operation — queries, mutations, subscriptions — goes to the same URL (commonly
/graphql), differentiated by the request body, not the path. - The status code doesn't tell you what happened. A GraphQL server typically returns
200for any syntactically valid request, whether the operation succeeded, partially succeeded, or failed at the resolver level. The real result lives in the body. - Clients choose exactly which fields to fetch. This solves REST's over-fetching/under-fetching problem by design, but means the same endpoint can return meaningfully different response shapes depending on what the client asked for — your tests need to match the query's actual field selection, not a fixed expected shape.
Testing GraphQL with the Frameworks You Already Have
Because a GraphQL request is just a POST with a JSON body, every framework already covered in this series works with no new library:
# pytest + requests
def test_graphql_get_user(api_session, base_url):
query = """
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
"""
response = api_session.post(
f"{base_url}/graphql",
json={"query": query, "variables": {"id": "1"}},
timeout=10,
)
body = response.json()
assert response.status_code == 200
assert "errors" not in body
assert body["data"]["user"]["id"] == "1"
// k6
const query = `
query GetUser($id: ID!) {
user(id: $id) { id name email }
}
`;
export default function () {
const res = http.post(
'https://api.example.com/graphql',
JSON.stringify({ query, variables: { id: '1' } }),
{ headers: { 'Content-Type': 'application/json' } }
);
check(res, {
'status is 200': (r) => r.status === 200,
'no errors': (r) => !JSON.parse(r.body).errors,
});
}
// REST Assured
@Test
void getUserViaGraphQL() {
String body = """
{"query": "query GetUser($id: ID!) { user(id: $id) { id name email } }", "variables": {"id": "1"}}
""";
given()
.contentType("application/json")
.body(body)
.when()
.post("/graphql")
.then()
.statusCode(200)
.body("errors", nullValue())
.body("data.user.id", equalTo("1"));
}
In Postman, select GraphQL as the request body type instead of raw JSON — Postman renders a dedicated query editor and, when introspection is enabled on the target server, autocompletes fields directly from the live schema.
Ready to shift left with your API testing?
Try our no-code API test automation platform free. Generate tests from OpenAPI, run in CI/CD, and scale quality.
Testing Mutations
Mutations follow the identical request shape — a query field in the body actually contains a mutation operation:
def test_create_post_mutation(api_session, base_url):
mutation = """
mutation CreatePost($title: String!, $body: String!) {
createPost(title: $title, body: $body) {
id
title
}
}
"""
response = api_session.post(
f"{base_url}/graphql",
json={"query": mutation, "variables": {"title": "foo", "body": "bar"}},
timeout=10,
)
body = response.json()
assert response.status_code == 200
assert "errors" not in body
assert body["data"]["createPost"]["title"] == "foo"
The errors Array: Testing Failure Cases
A negative test case in GraphQL asserts on the errors array's presence and content, not a 4xx status code:
def test_get_nonexistent_user_returns_error(api_session, base_url):
query = """
query GetUser($id: ID!) {
user(id: $id) { id name }
}
"""
response = api_session.post(
f"{base_url}/graphql",
json={"query": query, "variables": {"id": "999999"}},
timeout=10,
)
body = response.json()
assert response.status_code == 200 # still 200
assert len(body["errors"]) > 0
assert body["errors"][0]["path"] == ["user"]
Each error object typically includes a message, a path identifying which field in the query failed, and an extensions object many servers use for a machine-readable error code — check what your specific GraphQL server populates there and assert on it, since message text alone is a brittle thing to assert against.
Query Depth and Complexity Limiting
GraphQL's ability to nest queries arbitrarily deep is also a denial-of-service vector unique to it — a client can construct a single query requesting deeply nested, expensive-to-resolve data in one call. Test that your server actually rejects queries past its configured depth or complexity limit:
def test_excessively_deep_query_is_rejected(api_session, base_url):
deep_query = "query { user(id: \"1\") { posts { comments { author { posts { comments { author { id } } } } } } } }"
response = api_session.post(
f"{base_url}/graphql",
json={"query": deep_query},
timeout=10,
)
body = response.json()
assert len(body.get("errors", [])) > 0
This is a genuinely GraphQL-specific check — nothing in a REST API's design creates an equivalent single-request amplification risk this large.
Dedicated GraphQL Tools
- GraphiQL / Apollo Sandbox — in-browser query explorers, typically bundled with a GraphQL server for manual, ad-hoc exploration with schema-aware autocomplete via introspection.
- Postman — native GraphQL request body type with schema-aware autocomplete, usable for both manual exploration and, via Newman, CI-integrated automated tests.
- Insomnia — also has first-class GraphQL support with a dedicated query editor, similar to Postman's.
- Apollo Studio — a schema registry and monitoring platform for Apollo-based GraphQL APIs, useful for tracking schema changes and query performance over time rather than per-request assertions.
Common Pitfalls in GraphQL Testing
Free Guided worksheet
Build Your Testing Strategy in 30 Minutes
A structured worksheet that walks you through defining your testing strategy in 30 minutes. Cover architecture, tools, layers, and team responsibilities.
Download Free- Asserting only the status code. It's almost always
200— check theerrorsarray explicitly for both positive and negative cases. - Writing a query that requests more fields than the test actually asserts on. Match the query's field selection to what the test checks — an over-broad query makes the test slower and the intent less clear.
- Forgetting to test query depth/complexity limits. This is the GraphQL-specific security check that has no REST equivalent.
- Leaving introspection enabled in production without deciding to. Confirm whether it's intentionally on (useful for API-consuming partners) or should be restricted — don't leave it as an accidental default.
- Treating a partial-success response as a full failure or full success. GraphQL can return both
data(for the fields that resolved) anderrors(for the ones that didn't) in the same response — test that your client and assertions handle that correctly, not just the all-or-nothing case.
Frequently Asked Questions
How is testing a GraphQL API different from testing a REST API?
One endpoint instead of many, and HTTP 200 for almost every request — real success or failure lives in the data and errors fields of the response body, not the status code.
Do I need a special tool to test GraphQL APIs? No — since it's a POST with a JSON body, any HTTP-capable framework (pytest, REST Assured, Playwright, k6, Postman) tests GraphQL with no new library.
Why does my GraphQL API return 200 even when there's an error?
Standard behavior — resolver-level failures go into the response body's errors array instead of the HTTP status code.
What is query depth limiting and why should I test it? A check that the server rejects excessively nested queries, which can otherwise be used to construct a single, extremely expensive request — a GraphQL-specific denial-of-service vector.
Should introspection be enabled when testing a GraphQL API? Yes in development/test, where it powers autocomplete in tools like GraphiQL and Postman. In production it's commonly disabled or restricted since it exposes your full schema.
Can Total Shift Left generate tests for GraphQL APIs? Yes — REST, SOAP, and GraphQL are all supported for full test generation. gRPC is adapter-only, not included in that list.
Key Takeaways
- Check
dataanderrorsboth — a200status code alone proves almost nothing in GraphQL. - No new tooling required. pytest, REST Assured, Playwright, k6, and Postman all test GraphQL directly.
- Negative test cases assert on the
errorsarray, not a 4xx status code. - Query depth/complexity limiting is a GraphQL-specific security test with no REST equivalent — don't skip it.
- A response can be partially successful —
dataanderrorscan both be populated in the same response.
Related Articles
- API Testing with Python: pytest + requests Tutorial — the framework used in most examples above.
- k6 API Load Testing Tutorial — load testing a GraphQL endpoint the same way as REST.
- Types of API Testing — where GraphQL-specific checks fit into a broader test strategy.
- What Is API Contract Testing? — GraphQL's schema serves a similar contract role to an OpenAPI spec.
Generate GraphQL Test Coverage Automatically
Hand-writing query, mutation, and error-path tests for a growing GraphQL schema scales the same way any hand-written suite does. Total Shift Left generates test coverage for REST, SOAP, and GraphQL APIs directly from your schema, including negative and boundary cases — regenerated automatically as your schema evolves.
Start your free trial to see generated coverage for your own GraphQL API, or see plans and pricing if you're already evaluating.
Ready to shift left with your API testing?
Try our no-code API test automation platform free.