Guide: API Testing

GraphQL Code Generator can generate typed results for your GraphQL operations.


This tutorial uses GraphQL Yoga, but the practices are applicable to testing any GraphQL server.

Refer to GraphQL Codegen - Yoga example for a fully running example using GraphQL Yoga.

Installation

Install the following dependencies:

npm i graphql-yoga

Install the following development dependencies:

npm i -D typescript ts-node @graphql-codegen/cli jest @babel/core @babel/preset-env @babel/preset-typescript babel-jest @graphql-typed-document-node/core

Setup

First, create the following files with the boilerplate project set up.

./tsconfig.json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "Node16",
    "outDir": "dist"
  },
  "include": ["src/**/*"]
}
./babel.config.js
module.exports = {
  presets: [
    ['@babel/preset-env', { targets: { node: process.versions.node.split('.')[0] } }],
    '@babel/preset-typescript'
  ]
}
./jest.config.js
module.exports = {
  transform: { '^.+\\.ts': 'babel-jest' }
}

Next, create the codegen.ts file.

./codegen.ts
// eslint-disable-next-line import/no-extraneous-dependencies
import { type CodegenConfig } from '@graphql-codegen/cli'
 
const config: CodegenConfig = {
  schema: './src/yoga.ts',
  documents: ['src/**/*.ts'],
  generates: {
    './src/gql/': {
      preset: 'client-preset'
    }
  }
}
 
export default config

Up next, create a simple GraphQL Yoga server with a Query and Mutation root type.

./src/yoga.ts
import { createSchema, createYoga } from 'graphql-yoga'
 
const schema = createSchema({
  typeDefs: /* GraphQL */ `
    type Query {
      hello: String!
    }
 
    type Mutation {
      echo(message: String!): String!
    }
  `,
  resolvers: {
    Query: {
      hello: () => 'Hello world!'
    },
    Mutation: {
      echo: (_, args) => args.message
    }
  }
})
 
export const yoga = createYoga({
  schema
})

Writing tests

Now that we have a GraphQL Yoga server, we can write some tests.

./src/yoga.spec.ts
import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { type ExecutionResult, print } from 'graphql'
import { graphql } from './gql'
import { yoga } from './yoga'
 
function executeOperation<TResult, TVariables>(
  operation: TypedDocumentNode<TResult, TVariables>,
  ...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
): Promise<ExecutionResult<TResult>> {
  return Promise.resolve(
    yoga.fetch('http://yoga/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      body: JSON.stringify({
        query: print(operation),
        variables: variables ?? undefined
      })
    })
  ).then(response => response.json())
}
 
describe('Yoga Tests', () => {
  it('execute query operation', async () => {
    const HelloQuery = graphql(/* GraphQL */ `
      query HelloQuery {
        hello
      }
    `)
 
    const result = await executeOperation(HelloQuery)
 
    expect(result.data?.hello).toEqual('Hello world!')
  })
 
  it('execute mutation operation', async () => {
    const EchoMutation = graphql(/* GraphQL */ `
      mutation EchoMutation($message: String!) {
        echo(message: $message)
      }
    `)
 
    const result = await executeOperation(EchoMutation, {
      message: 'Ohayoo!'
    })
 
    expect(result.data?.echo).toEqual('Ohayoo!')
  })
 
  it('execute mutation operation (variant)', async () => {
    const EchoMutation = graphql(/* GraphQL */ `
      mutation EchoMutation($message: String!) {
        echo(message: $message)
      }
    `)
 
    const result = await executeOperation(EchoMutation, {
      message: 'Konbanwa'
    })
 
    expect(result.data?.echo).toEqual('Konbanwa')
  })
})

We can then generate the types for the referenced GraphQL operation using the GraphQL Code Generator CLI.

npx graphql-codegen

As the command was run, the TypeScript errors within the test file should have disappeared.

For convenience and writing tests it is recommended to run GraphQL Code Generator in watch mode.

npx graphql-codegen --watch

You can then run the tests using npx jest (or npx jest --watch) for watch mode.

Typed Execution Result helper function
import type { TypedDocumentNode } from '@graphql-typed-document-node/core'
import { type ExecutionResult, print } from 'graphql'
import { graphql } from './gql'
 
function executeOperation<TResult, TVariables>(
  operation: TypedDocumentNode<TResult, TVariables>,
  ...[variables]: TVariables extends Record<string, never> ? [] : [TVariables]
): Promise<ExecutionResult<TResult>> {
  return Promise.resolve(
    yoga.fetch('http://yoga/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Accept: 'application/json'
      },
      body: JSON.stringify({
        query: print(operation),
        variables: variables ?? undefined
      })
    })
  ).then(response => response.json())
}

The executeOperation function is a helper function that executes a GraphQL operation and returns a typed result. It can be used in any context, whether it is tests or server-to-server communication.

Conclusion

GraphQL Code Generator can enhance the developer experience by generating GraphQL operation types for unit and integration tests .