pothos-builder-tests
Wraps Pothos GraphQL schema-builder testing patterns: testing the SchemaBuilder output (lexicographicSortSchema + printSchema for snapshot tests), testing resolvers via the standard `graphql()` function from graphql-js (no server needed), integration with Apollo Server / GraphQL Yoga (Pothos emits standard graphql-js schemas), and code-first builder unit tests. Covers the SchemaBuilder API surface (queryType, mutationType, objectType, t.field, t.arg). Use when testing a Pothos-built schema before / alongside the server-runtime tests (apollo-server-test / graphql-yoga-test). Pairs with introspection-attack-surface-reference for production-config assertions via the underlying server.
pothos-builder-tests
Overview
Per pothos-graphql.dev, "the schema generated by Pothos is a standard graphql.js schema" - so any GraphQL test pattern works against it. The testing opportunity unique to Pothos is testing the builder output shape (which types, which fields, which deprecations) and the schema-stability over refactors.
When to use
Authoring
Install
npm install --save-dev @pothos/core graphqlBuild a test schema
Per pothos-graphql.dev/docs/guide:
import SchemaBuilder from '@pothos/core';
const builder = new SchemaBuilder({});
builder.queryType({
fields: (t) => ({
hello: t.string({
args: { name: t.arg.string() },
resolve: (_parent, { name }) => `hello, ${name || 'World'}`,
}),
}),
});
export const schema = builder.toSchema();The schema is a plain graphql.GraphQLSchema instance.
Unit-test a resolver via graphql()
import { graphql } from 'graphql';
import { schema } from './schema';
test('hello resolver', async () => {
const result = await graphql({
schema,
source: `{ hello(name: "alice") }`,
});
expect(result.errors).toBeUndefined();
expect(result.data?.hello).toBe('hello, alice');
});graphql() from graphql-js executes against the schema directly. No server. No HTTP. No middleware. Fast unit-test path.
Schema snapshot test
Catch any change to the public schema:
import { lexicographicSortSchema, printSchema } from 'graphql';
import { schema } from './schema';
test('schema snapshot', () => {
// lexicographicSortSchema produces deterministic ordering
const printed = printSchema(lexicographicSortSchema(schema));
expect(printed).toMatchSnapshot();
});When the schema changes, the snapshot fails and reviewers see the diff. Refactor-safe: a resolver rename that doesn't touch the schema doesn't trigger the snapshot.
Integration with Apollo Server
import { ApolloServer } from '@apollo/server';
import { schema } from './schema';
const server = new ApolloServer({ schema });
// ... use apollo-server-test patterns from hereIntegration with GraphQL Yoga
import { createYoga } from 'graphql-yoga';
import { schema } from './schema';
const yoga = createYoga({ schema });
// ... use graphql-yoga-test patterns from hereRunning
npm test
npm test -- --updateSnapshot # accept schema changesTesting the SchemaBuilder's plugin contract
Pothos has plugins for relay, prisma, errors, etc. Test each plugin's output:
import RelayPlugin from '@pothos/plugin-relay';
const builder = new SchemaBuilder<{ Context: AuthContext }>({
plugins: [RelayPlugin],
relay: { clientMutationId: 'optional' },
});
builder.queryType({ /* ... */ });
const schema = builder.toSchema();
test('relay plugin adds Node interface', () => {
const printed = printSchema(lexicographicSortSchema(schema));
expect(printed).toContain('interface Node');
});Testing context-required resolvers
test('me resolver returns current user', async () => {
const result = await graphql({
schema,
source: `{ me { id name } }`,
contextValue: { user: { id: 'u1', name: 'alice' } },
});
expect(result.data?.me).toEqual({ id: 'u1', name: 'alice' });
});
test('me resolver errors without auth', async () => {
const result = await graphql({
schema,
source: `{ me { id name } }`,
contextValue: { user: null },
});
expect(result.errors?.[0].message).toMatch(/authenticate/i);
});Parsing results
graphql() returns an ExecutionResult:
{
data?: { ... }, // null on error if root resolver failed
errors?: GraphQLError[],
extensions?: { ... }
}Snapshot tests use expect(printed).toMatchSnapshot(). The diff on failure shows exactly which types/fields changed.
CI integration
jobs:
pothos:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with: { node-version: '20' }
- run: npm ci
- run: npm test
- name: Schema snapshot must match
run: |
# Fails the build if snapshot would change
npx jest --ci tests/schema-snapshot.test.tsThe --ci flag prevents --updateSnapshot and fails on mismatch.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Skipping lexicographicSortSchema for snapshots | Non-deterministic order → snapshot flakes | Always sort |
Using --updateSnapshot in CI | Hides real schema changes | --ci flag only |
| Testing the builder API instead of the output | Pothos plugins drift; tests pass against deprecated builder | Test the printed schema |
| Single mega-snapshot for the whole schema | One field change → unrelated review pain | Per-domain snapshots (Query, Mutation, types) |
No contextValue in resolver tests | Tests bypass auth; passes for unauthenticated paths | Always pass test context |
| Server-runtime tests without Pothos-builder tests | Schema regressions slip through unit tests | Both layers needed |
| Testing resolvers in isolation only | Misses how plugins (relay, errors) reshape the response | Both unit + integration |
| Pothos schema diverges between dev and prod | Different plugins / configs | Build prod schema in test setup |