Contract Testing for GraphQL APIs with Mock Schemas
AI generated
{ }
type
GraphQL · Contract Testing · Mock Schema · CI/CD
Contract Testing for GraphQL APIs
with mock schemas as an executable contract

A GraphQL schema is already a machine-readable contract between frontend and backend. A mock schema makes that contract executable: frontend teams build against realistic test data long before the backend is finished, and breaking changes surface automatically instead of in production.

18 min read addMocksToSchema · Faker · Schema Diff · CI Node.js · Apollo · GraphQL Tools

1. Why contract testing works differently for GraphQL than REST

For REST APIs, contract testing needs additional tooling like Pact, because there's no machine-readable contract between client and server beyond documentation. In GraphQL, the contract is already part of the technology: the schema itself precisely defines which fields, types, and arguments exist, and every query is validated against exactly that schema. Contract testing for GraphQL APIs therefore doesn't need to reinvent this contract, it needs to make it usable before the actual implementation is done.

A mistake many teams make is confusing schema validation with contract testing. A query that's valid against the schema says nothing about whether the server actually returns meaningful data, whether pagination works correctly, or whether a frontend can handle realistic response shapes. Exactly this gap is closed by contract testing with mock schemas: an executable schema that returns plausible test data for every query without the real backend needing to exist.

2. What a mock schema delivers as an executable contract

A mock schema is a fully executable GraphQL schema where every resolver returns automatically generated or deliberately configured test data instead of hitting a real data source. The crucial difference from a static JSON fixture: a mock schema answers any valid query, including filters, nested fields and variables, exactly like a real server, just with generated instead of real data.

For contract testing, that means a frontend team can write exactly the same queries against the mock schema that will later run against the real backend, and get structurally identical responses. If the schema later changes incompatibly, that same query fails against both the mock schema and the real backend, which makes the contract between both sides concretely verifiable instead of merely documented in a README.

3. Setting up mock schemas with @graphql-tools/mock

The @graphql-tools/mock package is the standard way to automatically turn an existing schema, whether as an SDL file or an introspection result, into a fully executable mock schema. The addMocksToSchema function takes a schema and generates plausible default values for every scalar type: strings become lorem ipsum text, booleans alternate, IDs get unique generated values.

Without further configuration, this generic mocking already produces valid but not very meaningful responses. Serious contract testing needs targeted mock resolvers for the fields that actually matter domain-wise, such as prices, stock levels, or order status, while generic fields like internal IDs can safely stay random.


// mock-server.js — executable mock schema as a contract
const { makeExecutableSchema } = require('@graphql-tools/schema');
const { addMocksToSchema } = require('@graphql-tools/mock');
const { ApolloServer } = require('@apollo/server');
const { startStandaloneServer } = require('@apollo/server/standalone');
const fs = require('fs');

const typeDefs = fs.readFileSync('./schema.graphql', 'utf-8');
const schema = makeExecutableSchema({ typeDefs });

const mockedSchema = addMocksToSchema({
  schema,
  mocks: {
    // Generic scalar mocks apply everywhere unless overridden per field
    ID: () => Math.random().toString(36).slice(2, 10),
  },
});

const server = new ApolloServer({ schema: mockedSchema });
startStandaloneServer(server, { listen: { port: 4001 } });

4. Realistic test data: custom mock resolvers and Faker

Generic mocks are fine for an initial smoke test but fall short as soon as a frontend needs domain-plausible values, for example a realistic price instead of a random float with twenty decimal places. The resolvers block of addMocksToSchema lets you attach a Faker-based generation function to each type, tailored to concrete fields.

A realistic mock schema for a product catalog generates prices in a plausible range, product names from a commerce vocabulary instead of generic lorem ipsum, and stock levels that occasionally hit zero, so the frontend can test the "out of stock" case without waiting for a real record to reach that state.


// mock-resolvers.js — domain-realistic data instead of generic lorem ipsum
const { faker } = require('@faker-js/faker');

const mocks = {
  Product: () => ({
    name: faker.commerce.productName(),
    price: Number(faker.commerce.price({ min: 5, max: 500 })),
    // ~10% of mocked products are deliberately out of stock
    inStock: faker.number.int({ min: 0, max: 10 }) > 0,
  }),
  Query: () => ({
    products: () => Array.from({ length: 12 }, () => ({})),
  }),
};

module.exports = { mocks };

5. Frontend development in parallel with the backend

The main practical benefit of mock schemas is decoupling frontend and backend progress. As soon as a schema exists as an SDL draft, even if not a single resolver has been implemented in production yet, the frontend team can develop against a mock schema, build components, and even write automated tests. The backend team implements the real resolvers against the same schema in parallel.

This model works as contract testing because both teams work against the same contract: the schema. If the backend implementation diverges from the agreed schema, for example because a field turns out to need to be nullable, that becomes visible immediately at the next schema sync, long before frontend and backend are ever tested against each other for the first time, which in classic integration testing often happens only shortly before release.

6. Checking consumer contracts: which fields the client actually uses

An often overlooked part of contract testing for GraphQL APIs is the question of which fields of a schema are actually requested by real clients. A backend team wanting to remove a field needs to know whether any consumer, web frontend, mobile app, or third-party integration, actually uses that field, instead of relying on guesswork.

GraphQL Inspector and similar tools offer operation coverage reports for this: all known client queries are collected, usually from the .graphql files of the respective frontend repositories, and matched against the schema. The result is a list of unused fields that can safely be removed, and used fields whose removal would break a concrete, named consumer.

7. Keeping the mock schema and the real schema in sync

The biggest risk with contract testing using mock schemas is drift: the mock schema gets generated once from the real schema and then evolves independently, while the real backend schema moves in a different direction. Without a countermeasure, the mock schema loses its value as a contract within a few weeks, because it no longer describes the same thing as production.

The reliable solution is to never maintain the mock schema by hand, but to regenerate it fresh from the current SDL schema or a current introspection on every CI run. On top of that, a schema diff tool like GraphQL Inspector checks on every pull request whether the change is a breaking change, and blocks the merge if a field marked as used is removed or changed incompatibly.


# .github/workflows/contract-check.yml
name: GraphQL Contract Check
on: [pull_request]
jobs:
  schema-diff:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      # Compares the new schema against main and fails on breaking changes
      - run: npx graphql-inspector diff schema-main.graphql schema.graphql --fail-on-breaking

8. Standalone mock servers: GraphQL Faker vs. Prism

Besides programmatic integration via @graphql-tools/mock, there are standalone mock servers that expose a schema as an HTTP endpoint without any custom code. GraphQL Faker starts a running GraphQL server directly from an SDL file, with Faker-based mock data and a GraphiQL explorer, ideal for quick demos or when you don't want to set up a dedicated Node.js project for the mock server.

Prism, originally built for OpenAPI, now also supports GraphQL mocking and shines especially in teams already running REST and GraphQL APIs side by side and preferring one unified mocking tool for both contract types. For pure GraphQL projects, though, @graphql-tools/mock remains the more flexible choice, since custom mock resolvers can be defined directly in JavaScript or TypeScript instead of a separate configuration language.

9. Contract testing strategies compared

Depending on team size and schema maturity, different strategies suit contract testing for GraphQL APIs better.

Strategy Uses Strength Limitation
Generic mocking addMocksToSchema without custom resolvers Ready in minutes Data is not domain-meaningful
Faker-based mocking Custom resolvers + @faker-js/faker Realistic, testable edge cases Maintenance overhead per domain type
Operation coverage analysis GraphQL Inspector against client queries Shows actually used fields Requires access to all consumer repos
Schema diff in CI graphql-inspector diff --fail-on-breaking Prevents breaking changes before merge Doesn't catch semantic data bugs

In practice, mature GraphQL teams combine all four strategies: generic mocking for quick prototypes, Faker-based mocking for serious frontend development, operation coverage for safe deprecations, and schema diff in CI as the last, automated line of defense against unintended breaking changes.

Mironsoft

GraphQL contract testing, mock schemas and CI pipelines

Build frontend and backend in parallel, without surprises?

We build mock schemas with realistic test data, set up operation coverage reports, and integrate schema diff checks into your CI pipeline so breaking changes surface before the merge, not after.

Mock schema setup

Configure addMocksToSchema with domain-specific Faker resolvers

Coverage reports

Make used fields visible across all consumer repositories

CI schema diff

Breaking change detection as a mandatory pull request gate

10. Summary

Contract testing for GraphQL APIs with mock schemas takes advantage of the fact that GraphQL already carries a machine-readable contract, the schema itself. A mock schema enriched with addMocksToSchema and Faker-based resolvers makes that contract executable long before the real implementation exists, letting frontend and backend teams work independently against the same contract.

The contract only stays reliable as long as it's checked automatically: operation coverage reports show which fields real consumers use, and schema diff checks in the CI pipeline prevent breaking changes from silently reaching the main branch. Together, these building blocks replace expensive, manually coordinated integration testing with automated, repeatable contract testing.

Contract Testing for GraphQL APIs — The Essentials at a Glance

Schema as contract

GraphQL already carries the contract, the schema itself. Mock schemas make it executable.

Realistic test data

addMocksToSchema plus Faker resolvers instead of generic lorem ipsum for meaningful tests.

Coverage over guesswork

Operation coverage reports show which fields real consumers actually request.

Automated safeguards

Schema diff checks in CI prevent breaking changes before the merge, not after.

11. FAQ: Contract Testing for GraphQL APIs

1What is contract testing for GraphQL?
Automated verification that frontend and backend honor the same schema contract, executable via mock schemas.
2What is a mock schema?
An executable schema with generated instead of real data that answers any valid query like a real server.
3How do I create a mock schema?
With addMocksToSchema from @graphql-tools/mock, applied to an existing SDL schema.
4Why aren't generic mocks enough?
They aren't domain-meaningful. Faker-based custom resolvers deliver realistic values and edge cases.
5How does a mock schema help with parallel development?
Frontend builds against the mock schema while backend implements real resolvers, both against the same contract.
6What is operation coverage?
A match between the schema and actually used client queries, showing which fields are safe to remove.
7How do I avoid schema drift?
Never maintain the mock schema by hand, regenerate it fresh from the current schema on every CI run.
8What does GraphQL Inspector do?
Compares schema versions for breaking changes and builds operation coverage reports.
9GraphQL Faker vs. Prism?
GraphQL Faker is GraphQL-only, Prism also supports OpenAPI for mixed stacks.
10How do I integrate schema diff into CI?
With graphql-inspector diff --fail-on-breaking as a CI step that stops the build on breaking changes.