GraphQL & gRPC Testing

22GraphQL & gRPC Testing

What you will master here

  • GraphQL: queries, mutations, subscriptions, fragments
  • Testing GraphQL endpoints (HTTP POST → JSON)
  • Schema introspection + breaking-change detection
  • gRPC: Protobuf, unary vs streaming RPCs
  • Testing gRPC services (grpcurl, ghz, grpc-node)
  • When each is used vs REST

22.1 GraphQL essentials

// Request
POST /graphql
Content-Type: application/json
{
  "query": "query GetUser($id: ID!) { user(id: $id) { id name email orders { id total } } }",
  "variables": { "id": "u_123" }
}

// Response
{
  "data": {
    "user": {
      "id": "u_123",
      "name": "Alice",
      "email": "alice@test.com",
      "orders": [{ "id": "o_1", "total": 99.0 }]
    }
  }
}

22.2 Testing GraphQL with Playwright

test('GraphQL — fetch user with orders', async ({ request }) => {
  const r = await request.post('/graphql', {
    data: {
      query: `query($id: ID!){ user(id: $id){ id name orders { id total } } }`,
      variables: { id: 'u_123' },
    },
    headers: { Authorization: `Bearer ${token}` },
  });
  expect(r.status()).toBe(200);
  const body = await r.json();
  expect(body.errors).toBeUndefined();
  expect(body.data.user.email).toBe('alice@test.com');
  expect(body.data.user.orders.length).toBeGreaterThan(0);
});
GraphQL pitfallEven errors come back with HTTP 200! Check body.errors, not status. A query with a typo returns 200 + errors: [{...}].

22.3 GraphQL test patterns

22.4 Introspection + breaking-change detection

// Introspection query — fetches the whole schema
{ "query": "{ __schema { types { name fields { name type { name } } } } }" }

// Production tools to diff schema between releases:
npx graphql-inspector diff schema-old.graphql schema-new.graphql
// Flags removed fields, type changes — gates breaking changes in CI

22.5 gRPC essentials

// users.proto
syntax = "proto3";
service Users {
  rpc GetUser(GetUserRequest) returns (User);
  rpc ListUsers(ListUsersRequest) returns (stream User);   // server-streaming
}
message GetUserRequest { string id = 1; }
message User { string id = 1; string email = 2; }

22.6 Testing gRPC — tools

grpcurl — CLI equivalent of curl

grpcurl -plaintext -d '{"id": "u_123"}' \
  localhost:50051 users.Users/GetUser

# List methods on a server with reflection enabled
grpcurl -plaintext localhost:50051 list users.Users

Node test via grpc-js

import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';

const def = protoLoader.loadSync('users.proto');
const proto: any = grpc.loadPackageDefinition(def).users;
const client = new proto.Users('localhost:50051', grpc.credentials.createInsecure());

test('gRPC GetUser', (done) => {
  client.GetUser({ id: 'u_123' }, (err: any, res: any) => {
    expect(err).toBeNull();
    expect(res.email).toBe('alice@test.com');
    done();
  });
});

ghz — gRPC load tester

ghz --insecure --proto users.proto --call users.Users/GetUser \
  -d '{"id":"u_123"}' -c 50 -n 10000 localhost:50051

22.7 When each is used

API styleBest forTooling
RESTPublic APIs, broad client compatPostman / Playwright request / REST Assured
GraphQLUI-heavy apps; many fields, varied client needsSame HTTP tools; graphql-inspector for schema
gRPCInternal microservices, performance-criticalgrpcurl, ghz, language-specific clients
WebSocket / SSEReal-time pushPlaywright supports WebSocket events; specialised libs for SSE

Module 48 — GraphQL/gRPC Q&A

Why does a failed GraphQL query return HTTP 200?
GraphQL treats errors as application-level data — they ride in body.errors alongside any partial data. The HTTP layer just signals "the GraphQL endpoint received and processed your request". Always inspect body.errors instead of status.
How is GraphQL different from REST in testing strategy?
REST: many endpoints, each with own contract. GraphQL: one endpoint, schema-driven. Tests pivot from URL-based to query-based. Schema introspection enables automated breaking-change detection at the contract layer.
What's a mutation in GraphQL?
An operation that changes server state — analogous to POST/PUT/DELETE. Syntax: mutation { createUser(input: {...}) { id } }. Same HTTP shape as a query (POST /graphql) but the server runs the mutation resolvers.
How do you test a GraphQL subscription?
Open a WebSocket to the GraphQL endpoint, send a subscription operation, then trigger the event via a separate mutation. Assert the streamed payload matches. Tools: graphql-ws client, or Apollo Client's test utilities.
What is gRPC?
A binary RPC framework over HTTP/2 with Protobuf payloads. Strongly typed; clients + servers generated from .proto files. Lower latency and smaller payloads than JSON over REST, ideal for high-throughput microservices.
What's the difference between unary and streaming RPC?
Unary: one request → one response (like HTTP). Server-streaming: one request → stream of responses. Client-streaming: stream of requests → one response. Bidirectional: both stream. Used for chat, telemetry, file uploads.
What's grpcurl?
CLI tool for invoking gRPC methods — like curl for REST. Supports reflection so you can list services without a .proto file. Used for ad-hoc testing and CI smoke tests.
How do you load-test a gRPC service?
Use ghz — a Go-based gRPC load tester. Configure RPS, duration, payload, then it produces a report with latency percentiles. Equivalent of k6 for gRPC.
What's GraphQL introspection?
A query that returns the schema itself — types, fields, arguments. Clients and tools use it for auto-completion (GraphiQL) and contract checks. Often disabled in production for security.