GraphQL schema mocking with @graphql-tools/schema and @graphql-tools/mock: working versions (tested 2026-10-15)
Build an executable schema with makeExecutableSchema and layer deterministic mocks, a MockStore, MockList and partial real resolvers on top with addMocksToSchema, including mutations that write to the store.
passed5 passed · 0 failed2026-10-15 20:08 UTC- Status
- passed
- Last passed
- 2026-10-15 20:08 UTC
- Tests
- 5 / 5
- Language
- TypeScript
- Runtime
- node v22.23.3
npm install --no-audit --no-fund --ignore-scripts=false npm test
node v22.23.3 linux 6.17.0-1022-azure x64 runner: github-actions 38799498972
The code
Exactly the files that passed. Your agent can pull them with get_recipe("graphql-tools-schema-mock") (install), or see them on GitHub.
{
"name": "graphql-tools-schema-mock",
"private": true,
"type": "module",
"scripts": {
"test": "node --import tsx --test test/*.test.ts"
},
"dependencies": {
"@graphql-tools/schema": "10.1.5",
"@graphql-tools/mock": "9.1.18",
"graphql": "17.0.2"
},
"devDependencies": {
"@types/node": "26.6.5",
"tsx": "4.23.15"
}
}
import { makeExecutableSchema } from "@graphql-tools/schema";
import { addMocksToSchema, createMockStore, MockList } from "@graphql-tools/mock";
import { graphql, type GraphQLSchema } from "graphql";
export const typeDefs = /* GraphQL */ `
type Course { id: ID!, title: String!, hours: Int!, instructor: Instructor!, tags: [String!]! }
type Instructor { id: ID!, name: String!, courses: [Course!]! }
type Query { courses(limit: Int): [Course!]!, course(id: ID!): Course, me: Instructor! }
type Mutation { renameCourse(id: ID!, title: String!): Course! }
`;
/** Real resolvers for the parts that exist; mocks fill in the rest (handy for frontend work before the API is done). */
export function buildSchema(seed = 1) {
const base = makeExecutableSchema({ typeDefs });
let n = seed;
const nextInt = () => (n = (n * 48271) % 2147483647); // deterministic PRNG instead of Math.random
const mocks = {
ID: () => `id-${nextInt() % 1000}`,
Int: () => (nextInt() % 100) + 1,
String: () => `str-${nextInt() % 1000}`,
Course: () => ({ tags: () => new MockList(2) }),
Instructor: () => ({ name: "Phil Morin" }),
};
const store = createMockStore({ schema: base, mocks });
const schema = addMocksToSchema({
schema: base,
store,
preserveResolvers: true,
resolvers: (s) => ({
Query: {
courses: (_: unknown, { limit }: { limit?: number }) => Array.from({ length: limit ?? 3 }, (_, i) => s.get("Course", `c${i + 1}`)),
course: (_: unknown, { id }: { id: string }) => (id.startsWith("c") ? s.get("Course", id) : null),
},
Mutation: {
renameCourse: (_: unknown, { id, title }: { id: string; title: string }) => {
s.set("Course", id, "title", title);
return s.get("Course", id);
},
},
}),
});
return { schema, store };
}
export const run = (schema: GraphQLSchema, source: string, variableValues?: Record<string, unknown>) =>
graphql({ schema, source, variableValues }) as Promise<{ data?: any; errors?: readonly { message: string }[] }>;
import { test } from "node:test";
import assert from "node:assert/strict";
import { buildSchema, run } from "../src/schema.ts";
test("mocked query returns shape-correct data", async () => {
const { schema } = buildSchema();
const r = await run(schema, "{ courses(limit: 2) { id title hours tags instructor { name } } }");
assert.equal(r.errors, undefined);
assert.equal(r.data.courses.length, 2);
for (const c of r.data.courses) {
assert.equal(typeof c.title, "string");
assert.ok(Number.isInteger(c.hours) && c.hours >= 1 && c.hours <= 100);
assert.equal(c.tags.length, 2);
assert.equal(c.instructor.name, "Phil Morin");
}
assert.deepEqual(r.data.courses.map((c: { id: string }) => c.id), ["c1", "c2"]);
});
test("mocks are deterministic for a seed", async () => {
const q = "{ course(id: \"c1\") { title hours } }";
const a = await run(buildSchema(7).schema, q);
const b = await run(buildSchema(7).schema, q);
assert.deepEqual(a.data, b.data);
});
test("store keeps entities consistent across queries", async () => {
const { schema } = buildSchema();
const first = await run(schema, '{ course(id: "c1") { title instructor { id } } }');
const again = await run(schema, '{ courses(limit: 1) { title instructor { id } } }');
assert.deepEqual(again.data.courses[0], first.data.course);
});
test("mutation writes to the mock store", async () => {
const { schema, store } = buildSchema();
const m = await run(schema, 'mutation($t: String!) { renameCourse(id: "c2", title: $t) { id title } }', { t: "CITS Orientation" });
// graphql 17 returns null-prototype result objects, so spread before deepEqual
assert.deepEqual({ ...m.data.renameCourse }, { id: "c2", title: "CITS Orientation" });
assert.equal(store.get("Course", "c2", "title"), "CITS Orientation");
const q = await run(schema, '{ course(id: "c2") { title } }');
assert.equal(q.data.course.title, "CITS Orientation");
});
test("custom resolver can return null and validation errors still apply", async () => {
const { schema } = buildSchema();
assert.equal((await run(schema, '{ course(id: "x9") { id } }')).data.course, null);
const bad = await run(schema, "{ courses { nope } }");
assert.match(bad.errors![0].message, /Cannot query field "nope"/);
});
Connect to ProvenPin
Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp
{ "mcpServers": { "provenpin": { "url": "https://mcp.provenpin.com/mcp" } } }{ "servers": { "provenpin": { "type": "http", "url": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "serverUrl": "https://mcp.provenpin.com/mcp" } } }{ "mcpServers": { "provenpin": { "type": "streamableHttp", "url": "https://mcp.provenpin.com/mcp" } } }Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.
Related stacks
Apollo Server 5 resolvers with per-request DataLoader batching
An Apollo Server 5 schema whose nested resolvers use per-request DataLoader instances to batch and dedupe lookups (no N+1), with GraphQLError extension codes and cache invalidation after a mutation, tested in-process with executeOperation.
GraphQL Yoga 5 schema, context and errors tested via fetch
Build a GraphQL Yoga server with SDL resolvers, per-request context auth and GraphQLError codes, tested in-process with yoga.fetch.
graphql-js code-first schema execution
Build a code-first graphql-js schema with an object type, a custom Date scalar, arguments and variables, then execute it and inspect validation and resolver errors offline.
graphql-request client with typed operations, mocked by MSW graphql handlers
Call a GraphQL API with graphql-request (variables, headers, error handling) and test offline with MSW's graphql.link(url).query/mutation handlers (msw 3 API). graphql stays on 16.x: graphql-request 7 peers on graphql 14 - 16.
All @graphql-tools/schema recipesAll @graphql-tools/mock recipesAll graphql recipes