Integration testing
Utilities for testing Apollo Server
Apollo Server uses a multi-step request pipeline to validate and execute incoming GraphQL operations. This pipeline supports integration with custom plugins at each step, which can affect an operation's execution. Because of this, it's important to perform integration tests with a variety of operations to ensure your request pipeline works as expected.
There are two main options for integration testing with Apollo Server:
Using
ApolloServer
'sexecuteOperation
method.Setting up an HTTP client to query your server.
Testing using executeOperation
Apollo Server's executeOperation
method enables you to run operations through the request pipeline without sending an HTTP request.
The executeOperation
method accepts the following arguments:
An object that describes the GraphQL operation to execute.
This object must include a
query
field specifying the GraphQL operation to run. You can useexecuteOperation
to execute both queries and mutations, but both use thequery
field.
An optional argument that is passed in to the
ApolloServer
instance'scontext
function.
Below is a simplified example of setting up a test using the JavaScript testing library Jest:
1// For clarity in this example we included our typeDefs and resolvers above our test,
2// but in a real world situation you'd be importing these in from different files
3const typeDefs = gql`
4 type Query {
5 hello(name: String): String!
6 }
7`;
8
9const resolvers = {
10 Query: {
11 hello: (_, { name }) => `Hello ${name}!`,
12 },
13};
14
15it('returns hello with the provided name', async () => {
16 const testServer = new ApolloServer({
17 typeDefs,
18 resolvers
19 });
20
21 const result = await testServer.executeOperation({
22 query: 'query SayHelloWorld($name: String) { hello(name: $name) }',
23 variables: { name: 'world' },
24 });
25
26 expect(result.errors).toBeUndefined();
27 expect(result.data?.hello).toBe('Hello world!');
28});
Note that when testing, any errors in parsing, validating, and executing your GraphQL operation are returned in the errors
field of the operation result. As with any GraphQL response, these errors are not thrown.
Unlike with a normal instance of ApolloServer
, you don't need to call start
before calling executeOperation
. The server instance calls start
automatically for you if it hasn't been called already, and any startup errors are thrown.
To expand on the example above, here's a full integration test being run against a test instance of ApolloServer
. This test imports all of the important pieces to test (typeDefs
, resolvers
, dataSources
) and creates a new instance of ApolloServer
.
1it('fetches single launch', async () => {
2 const userAPI = new UserAPI({ store });
3 const launchAPI = new LaunchAPI();
4
5 // create a test server to test against, using our production typeDefs,
6 // resolvers, and dataSources.
7 const server = new ApolloServer({
8 typeDefs,
9 resolvers,
10 dataSources: () => ({ userAPI, launchAPI }),
11 context: () => ({ user: { id: 1, email: 'a@a.a' } }),
12 });
13
14 // mock the dataSource's underlying fetch methods
15 launchAPI.get = jest.fn(() => [mockLaunchResponse]);
16 userAPI.store = mockStore;
17 userAPI.store.trips.findAll.mockReturnValueOnce([
18 { dataValues: { launchId: 1 } },
19 ]);
20
21 // run the query against the server and snapshot the output
22 const res = await server.executeOperation({ query: GET_LAUNCH, variables: { id: 1 } });
23 expect(res).toMatchSnapshot();
24});
The example above includes a test-specific context
function, which provides data directly to the ApolloServer
instance instead of calculating it from the request's context.
To use your server's defined context
function, you can pass a second argument to executeOperation
, which is then passed to your server's context
function. Note that to use your server's context
function you need to put together an object with the correct middleware-specific context fields for your implementation.
For examples of both integration and end-to-end testing we recommend checking out the tests included in the Apollo fullstack tutorial.
End-to-end testing
Instead of bypassing the HTTP layer, you might want to fully run your server and test it with a real HTTP client. Apollo Server doesn't provide built-in support for this at this time.
You can run operations against your server using a combination of any HTTP or GraphQL client such as supertest
or Apollo Client's HTTP Link . There are also community packages available such as apollo-server-integration-testing
, which uses mocked Express request and response objects.
Below is an example of writing an end-to-end test using the apollo-server
package and supertest
:
1/// we import a function that we wrote to create a new instance of Apollo Server
2import { createApolloServer } from '../server';
3
4// we will use supertest to test our server
5import request from 'supertest';
6
7// this is the query we use for our test
8const queryData = {
9 query: `query sayHello($name: String) {
10 hello(name: $name)
11 }`,
12 variables: { name: 'world' },
13};
14
15describe('e2e demo', () => {
16 let server, url;
17
18 // before the tests we will spin up a new Apollo Server
19 beforeAll(async () => {
20 // Note we must wrap our object destructuring in parentheses because we already declared these variables
21 // We pass in the port as 0 to let the server pick its own ephemeral port for testing
22 ({ server, url } = await createApolloServer({ port: 0 }));
23 });
24
25 // after the tests we will stop our server
26 afterAll(async () => {
27 await server?.close();
28 });
29
30 it('says hello', async () => {
31 // send our request to the url of the test server
32 const response = await request(url).post('/').send(queryData);
33 expect(response.errors).toBeUndefined();
34 expect(response.body.data?.hello).toBe('Hello world!');
35 });
36});
You can also view and fork this complete example on CodeSandbox: