Engineering

Integration Testing with Apollo and GraphQL

Andrew Javidfar

June 10th, 2022

In this blog post, I will introduce the concept of integration testing and its purpose as well as dive into integration testing with Apollo GraphQL.

What are API Endpoint Integration Tests

Integration testing is great for testing an API endpoint as a black box (see figure below) while mocking everything else. This allows us to control everything external to the API and dictate what should happen within the test. To better understand the scope of an integration test, let's consider the following example:

Let’s say we have an API that manages a list of books, saves them in a database, and notifies an external API. A traditional unit test will tell us what happens when we call an external API or what happens when we save an item.

However, let’s consider the scenario where we want to test the insertion of two entries, back to back, and then perform a read query and expect two results. Likewise, what if we run a bunch of graphql queries/mutations and want to make sure the API as a whole still behaves as expected. This is where we want to utilize integration tests.

Apollo GraphQL Integration Tests

Apollo Server uses a multi-step request pipeline to validate and execute incoming GraphQL operations. We want to mock external API calls and database dependencies while being able to test our APIs by sending graphql queries/mutations through our test. At Pulse Analytics, we use the executeOperation method provided by apollo-server for our current integration tests.

The diagram below shows the different modules covered within the integration test scope.

Integration Test Scope

Testing using executeOperation

Apollo Server’s executeOperation method enables us to run operations through the request pipeline without sending an HTTP request.

The operation takes in two parameters: A query field specifying the GraphQL operation to run. An optional context argument that is passed to the ApolloServer context function.

A simplified example from Apollo is shown below for setting up a test using the JavaScript testing library Jest:


// For clarity in this example we included our typeDefs and resolvers above 
// our test, but in a real world situation you'd be importing these in from 
// different files
import gql from 'graphql-tag'

const typeDefs = gql`
  type Query {
    hello(name: String): String!
  }
`
const resolvers = {
  Query: {
    hello: (_, { name }) => `Hello ${name}!`,
  },
}

describe('hello resolver', () => {
  test('returns hello with the provided name', async () => {
    const testServer = new ApolloServer({
      typeDefs,
      resolvers
    })

    const result = await testServer.executeOperation({
      query: 'query SayHelloWorld($name: String) { hello(name: $name) }',
      variables: { name: 'world' },
    });

    expect(result.errors).toBeUndefined();
    expect(axios.get).toHaveBeenCalledWith(url)
    expect(result.data?.hello).toBe('Hello world!')
  })
})

Pulse Integration Testing Standards

For our codebase, we have a modified version of the test shown above where we separate out the testServer object into a shared utility file that can be used across all integration tests. We import and pass all of our typeDefs and resolvers to this util and output an instance of an ApolloServer to be used as our test server. The benefit of this is that it allows us to add all of our typedefs and resolvers to a single test server rather than having to spin up a new test server for each new API. Likewise, it gives us access to all typedefs and resolvers within the test so if we have a new API that is somehow dependent on an existing API, we can still test that dependency since the test server knows about them.

Once this initial setup is done, we can perform the executeOperation, passing in our query argument and parsing the response object to get the response body data. We can then compare the response data to the mocked data to verify that our query has returned the correct data.

Below is an example of an indications query integration test:

Query:

import gql from `graphql-tags`
export const GET_BOOKS = gql`
  query getBooks {
    books {
      id
      name
    }
  }
`

TypeDefs:

import { gql } from 'apollo-server-express'
const queries = gql`
  type Query {
    Books: [Book]
  }
  type Book {
    _id: ID!
    name: String
  }
`
module.exports = [queries]

Resolver (MongoDb):

module.exports = ({ db }) => db.collection('books').find().toArray()

Test Server:

import { ApolloServer } from 'apollo-server-express'    
import typeDefs from './typeDefs'
import resolvers from './resolvers'

const constructTestServer = () => {
  const server = new ApolloServer({
    typeDefs,
    resolvers,
  })
  return server
}
export default constructTestServer

Books API Integration Test:

import axios from 'axios'

import { GET_BOOKS } from './queries.js'
import { mockBooks } from './mockBookData'
import constructTestServer from './constructTestServer'

jest.mock('axios')

describe(‘query books’, () => {
  test('fetches list of books', async () => {
    //Create test server
    const server = constructTestServer()

    //Mock axios.get()
    axios.get.mockResolvedValueOnce({ data: mockBooks })

    //Call resolver by executing operation
    const res = await server.executeOperation({ query: GET_BOOKS })
    const responseObj = JSON.parse(JSON.stringify(res.data.books))

    //First, match mocked return with response
    expect(responseObj).toMatchObject(mockBooks)
  })
})

Integration Testing Standards and Future Development

The eventual goal with integration tests is to get complete API coverage for all of the GraphQL queries/mutations in our application. With this, a goal will be to create high level scaffolding that would allow us to quickly spin up integration tests by abstracting as much from the test as possible so that we can minimize testing boilerplate. What’s shown above is a great first step toward this goal and we will continue to expand on this testing foundation. A few goals we would like to hit and potentially write about in future blog posts are:

  • Implement Jest snapshots into the tests
  • Improve data mocking if possible to simplify testing
  • Potentially refactor the codebase to follow Apollo’s integration test example repo which really isolates individual components and provides flexibility but with it comes a level of complexity that will need to be discussed among the team.
Pulse Digital Logo
© Copyright2025 Pulse Analytics, LLC. All Rights Reserved