Skip to content

Codegen and Testing

The previous lesson left a gap: schema-first SDL does not, by itself, force your resolvers to match it. This lesson closes that gap from two directions. Code generation makes the compiler enforce the contract before you run anything; testing proves the service behaves once it does run.

GraphQL Code Generator — types from the schema

Section titled “GraphQL Code Generator — types from the schema”

GraphQL Code Generator reads your SDL and emits TypeScript types that mirror it exactly. On the server it produces a Resolvers type so every resolver is checked against the field it implements. On the client it produces typed result and variable types for each operation you write. You author a small config and run a command; the types regenerate whenever the schema changes.

codegen.yml
schema: ./schema.graphql
generates:
./src/generated/graphql.ts:
plugins:
- typescript
- typescript-resolvers

With that config, the generator turns this SDL:

type Track {
id: ID!
title: String!
durationSeconds: Int!
}
type Query {
track(id: ID!): Track
}

into TypeScript you import and apply to your resolver map:

import type { Resolvers } from './generated/graphql';
// `Resolvers` is generated from the SDL. If `track` returned the wrong
// shape — or forgot a non-null field — this would be a compile error.
export const resolvers: Resolvers = {
Query: {
track: (_parent, args) => ({
id: args.id,
title: 'Walking the Graph',
durationSeconds: 214,
}),
},
};

Now schema-first gets the safety that code-first had for free: the SDL stays the readable contract, and the compiler refuses to build if a resolver drifts away from it. Change durationSeconds to a String! in the SDL and the resolver above stops compiling until you fix it. That is the whole point of codegen — make drift impossible to ship.

Type checks prove the shapes are right. Tests prove the behaviour is right. There are two layers worth writing.

  • Unit tests for resolvers. A resolver is just a function of (parent, args, context, info). Call it directly with fake inputs and assert on the return value. These are fast and pin down a single resolver’s logic — no schema, no network.
  • Integration tests for operations. Build the executable schema and run a real GraphQL operation against it with the graphql function, then assert on result.data and result.errors. This exercises parsing, validation, the right resolvers firing, and nullability — the contract end to end.
flowchart TD
  SDL["SDL schema"]
  Codegen["GraphQL Code Generator"]
  Types["Generated Resolvers + operation types"]
  Unit["Unit tests: call resolver functions directly"]
  Integration["Integration tests: execute operations with graphql()"]
  Confidence["Trusted, shippable service"]
  SDL --> Codegen
  Codegen --> Types
  Types --> Unit
  Types --> Integration
  Unit --> Confidence
  Integration --> Confidence
Codegen guards the shapes at compile time; unit and integration tests guard the behaviour at run time.

The integration layer is the one people skip and regret. It is the only test that proves a query a client would actually send returns what the schema promises — including that non-null fields never come back null and that arguments thread through to the resolver.

The demo below is an integration test in miniature. It builds a tiny schema, executes a real query against it, and then runs a few assertions — exactly what a test framework like Vitest does under the hood with expect. A small assert helper prints a pass or fail line for each check. Press Run.

JavaScript

Read the output as a test report. Each PASS line is one expect that held. The id assertion is the interesting one: it proves the argument "t1" actually reached the resolver and came back in the data. Swap the expected 214 for 999 and re-run to watch a FAIL line appear — that red line is exactly what catches a regression before it ships.

What does the typescript-resolvers plugin of GraphQL Code Generator produce?
What kind of error does code generation catch that a unit test does not?
How does an integration test exercise the schema?
In the demo, what does the id assertion specifically prove?