API Testing
Intermediate 13 min read API Testing

GraphQL Testing

GraphQL testing verifies the typed query layer: operations, variables, the schema and error handling. One endpoint serves a schema, so tests focus on which fields an operation selects, whether arguments behave, and how the resolver graph resolves nested data.

GraphQLqueriesmutationsintrospectionschemaresolvers
Introduction

What is this?

GraphQL testing verifies the typed query layer: operations (queries and mutations), variables, schema and error handling. Instead of many REST endpoints, one endpoint serves a schema, so tests focus on which fields an operation selects, whether arguments behave, and how the resolver graph resolves nested data. Coverage includes both the shape of responses and the schema contract itself.

Motivation

Why do you need it?

  • The schema is the contract: testing it catches removed fields and changed types before clients break.
  • Queries fetch nested data in one round trip, so tests verify the whole resolver graph at once.
  • Variables isolate the operation from input data, making tests repeatable and data-driven.
  • GraphQL error handling is non-standard (200 with an errors array), so it must be tested explicitly.
  • Introspection lets tests assert the schema automatically and document what is actually available.
Think about it

Mental model

Imagine

A menu with categories and dishes

Think of

A GraphQL schema

The menu tells you exactly what can be ordered and how each dish is composed; you pick only what you want and the kitchen sends precisely that. The schema is the menu — introspection reads it, and tests confirm every dish you order arrives as described.

Mechanics

How it works

  • Write a query or mutation operation that selects the exact fields you want.
  • Send it as an HTTP POST (or via a client like Apollo or graphql-request) with optional variables.
  • Assert on data paths returned by the resolver graph and on the errors array when they should occur.
  • Validate the schema with introspection or a schema snapshot so type changes fail CI immediately.
  • Test arguments and nullability: required args, invalid types, and fields that legitimately return null.

An operation travels through validation, resolvers and the response envelope.

Operation
POST /graphql
Schema validation
Resolvers
Response
Assertions
Pass or fail
Reference

Key commands

curl -s -X POST https://api.example.com/graphql -H 'Content-Type: application/json' -d '{"query":"{ __typename }"}'

Send a minimal introspection query to confirm the endpoint is alive.

{"data":{"__typename":"Query"}}
curl -s -X POST https://api.example.com/graphql -H 'Content-Type: application/json' -d @query.json | jq .

Post an operation from a file and pretty-print the JSON response.

npx graphql-schema-linter schema.graphql

Lint the schema for common problems like missing descriptions or casing issues.

npx @graphql-inspector/cli validate schema.graphql new-schema.graphql

Diff two schema versions and fail on breaking changes like removed fields.

npx apollo schema:download schema.graphql --endpoint https://api.example.com/graphql

Download the live schema for diffing against the codebase's version.

Hands-on

Examples

Run a query with variables and assert the result

Run a query with variables and assert the result
const query = [
"query Order($id: ID!) {",
" order(id: $id) {",
" id",
" status",
" lineItems { sku quantity }",
" }",
"}",
].join("\n");
const res = await fetch("https://api.example.com/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, variables: { id: "42" } }),
});
const json = await res.json();
if (json.errors) throw new Error(json.errors[0].message);
console.assert(json.data.order.status === "shipped", "order should be shipped");
console.assert(json.data.order.lineItems.length === 2, "expected two line items");

The operation declares a required ID! variable and selects only the fields the test needs. The POST body carries the query and variables as JSON. GraphQL can return HTTP 200 even when a resolver failed, so the test checks json.errors first, then asserts on the exact field paths in json.data. The join approach keeps the query readable without embedded backticks.

Real world

In production

  • Schema snapshots and linting run in CI so breaking type changes are caught at pull-request time.
  • Mutation tests verify idempotency and side effects that a REST PUT endpoint would otherwise cover.
  • Performance tests measure query depth and over-fetching, driving resolver-level fixes.
  • Introspection-based tests prove every field in the changelog actually exists and resolves.
Guidelines

Best practices

  • Assert on the specific field paths you need rather than the entire response object.
  • Use variables for input and keep fixtures out of the query strings.
  • Always assert the errors array for failing operations — a 200 can still mean a failed request.
  • Snapshot the schema in CI and review diffs like code, with the API team.
  • Test nullability explicitly: a field that may be null should be tested both present and absent.
Watch out

Common mistakes

  • Forgetting that GraphQL returns HTTP 200 with an errors array — checking status alone hides failures.
  • Requesting top-level fields without checking nested selections or resolver behavior.
  • Hard-coding large IDs and values in queries, making tests fragile and unrepeatable.
  • Never testing mutations with side effects, so write operations break in production undetected.
Root cause analysis

Common issues

Error
Cannot query field 'name' on type 'User'
Root cause

The field was renamed, removed, or requires a different selection set.

Fix

Introspect the type, update the query to the current field name, and re-run.

Error
Variable '$id' of required type 'ID!' was not provided
Root cause

The operation declares a required variable that was not passed.

Fix

Add the variable to the variables object and ensure the JSON key matches the declared name.

Error
HTTP 200 but result.errors is populated
Root cause

The operation ran but a resolver failed or validation errored; GraphQL still returns 200.

Fix

Assert on errors in tests and surface resolver exceptions from the server logs.

When things break

Debugging tips

  • Read the errors array message and locations — they point at the exact field and line in the operation.
  • Use an IDE with a GraphQL extension or the explorer UI to validate queries before writing tests.
  • Check extensions and context in the response for resolver timing and tracing data.
  • Run the operation against the introspection endpoint to confirm the field exists and its type.
Interviews

Interview checkpoint

Practice

Hands-on lab

Test Queries, Mutations and the Schema
40 min · intermediate
In progress
Objectives
  • Send a query and assert on the returned data paths
  • Parameterize an operation with variables
  • Trigger an error and assert the errors array
  • Validate the schema with introspection or a schema linter

Test Queries, Mutations and the Schema

40 min · intermediate

0/5 steps
0%
sandbox · Linux sandbox
$
Try these commands
Test yourself

Quick quiz

0/3
1

Which part of a GraphQL operation is used to pass dynamic input without hard-coding it?

2

A GraphQL response with an errors array always returns HTTP 400.

3

Your query selects user { id name } but the response only contains null for name.

Quick reference

Cheat sheet

Operation essentials
query { user(id: "42") { id name } }Read data
mutation { createOrder(items: [1,2]) { id } }Change state
query ($id: ID!) { ... }Declare variables
{"query":"...","variables":{"id":"42"}}POST body shape
Schema tooling
npx graphql-schema-linter schema.graphqlLint the schema
npx @graphql-inspector/cli validate a bFail on breaking diffs
npx apollo schema:download s.graphql --endpoint <url>Fetch live schema
Go deeper

Official documentation