GraphQL APIs are surprisingly easy to test in CI pipelines because they often expose a single endpoint that acts as the gateway to all your data.

Here’s a simplified setup for testing a GraphQL API using Jest and graphql-request in a CI environment. Imagine you have a basic GraphQL schema and a simple query to fetch user data.

// src/schema.js
const { gql } = require('apollo-server');

const typeDefs = gql`
  type User {
    id: ID!
    name: String!
    email: String
  }

  type Query {
    user(id: ID!): User
  }
`;

const resolvers = {
  Query: {
    user: (parent, { id }) => {
      // In a real app, this would fetch from a database
      const users = {
        '1': { id: '1', name: 'Alice', email: 'alice@example.com' },
        '2': { id: '2', name: 'Bob', email: 'bob@example.com' },
      };
      return users[id];
    },
  },
};

module.exports = { typeDefs, resolvers };

And here’s how you might set up your Apollo Server:

// src/server.js
const { ApolloServer } = require('apollo-server');
const { typeDefs, resolvers } = require('./schema');

const server = new ApolloServer({ typeDefs, resolvers });

server.listen().then(({ url }) => {
  console.log(`🚀 Server ready at ${url}`);
  // In a real CI, you'd likely want to capture this URL
  // or have a fixed port for testing.
});

module.exports = server; // Export for testing

Now, let’s write a test that runs against this server. We’ll use Jest as our test runner and graphql-request to make the actual GraphQL calls.

First, install the necessary packages:

npm install --save-dev jest graphql-request @apollo/server

Create a jest.config.js file:

// jest.config.js
module.exports = {
  testEnvironment: 'node',
  setupFilesAfterEnv: ['./jest.setup.js'],
};

And a jest.setup.js file to start and stop your Apollo Server for testing:

// jest.setup.js
const { ApolloServer } = require('@apollo/server');
const { typeDefs, resolvers } = require('./src/schema');

const PORT = 4001; // Use a fixed port for testing
let server;
let url;

beforeAll(async () => {
  server = new ApolloServer({ typeDefs, resolvers });
  const app = require('express')();
  await server.start();
  const expressMiddleware = require('@apollo/server/express4').express4.getMiddleware;
  app.use('/graphql', expressMiddleware(server));

  await new Promise(resolve => app.listen({ port: PORT }, resolve));
  url = `http://localhost:${PORT}/graphql`;
  console.log(`🚀 Test server ready at ${url}`);
});

afterAll(async () => {
  await server.stop();
});

// Make the URL available to tests
global.GRAPHQL_URL = url;

Finally, your test file (src/user.test.js):

// src/user.test.js
const { request } = require('graphql-request');

describe('User API', () => {
  it('should fetch user details correctly', async () => {
    const query = `
      query GetUser($userId: ID!) {
        user(id: $userId) {
          id
          name
          email
        }
      }
    `;
    const variables = { userId: '1' };

    const response = await request(global.GRAPHQL_URL, query, variables);

    expect(response.user).toEqual({
      id: '1',
      name: 'Alice',
      email: 'alice@example.com',
    });
  });

  it('should return null for non-existent user', async () => {
    const query = `
      query GetUser($userId: ID!) {
        user(id: $userId) {
          id
        }
      }
    `;
    const variables = { userId: '999' };

    const response = await request(global.GRAPHQL_URL, query, variables);

    expect(response.user).toBeNull();
  });
});

To run these tests, you’d add a script to your package.json:

"scripts": {
  "test": "jest"
}

And then run npm test.

For a CI pipeline (e.g., GitHub Actions, GitLab CI), you’d have a workflow file that checks out your code, installs dependencies, and runs npm test. The key here is that the tests are self-contained: they start their own GraphQL server, run queries against it, and shut it down. This makes them highly portable and reliable in an ephemeral CI environment.

The real power in CI comes from the fact that you’re testing the contract of your API—the schema—and your resolvers at the same time, without needing to spin up a full external database or other dependencies. If your resolvers correctly interpret the GraphQL query and return data conforming to your schema, your tests pass.

The next logical step is to integrate schema validation into your CI pipeline. This involves using tools like graphql-tools or apollo-server’s built-in validation to ensure your queries themselves are syntactically correct and adhere to your schema before they even hit your resolvers.

Want structured learning?

Take the full Graphql-tools course →