Docs
Launch GraphOS Studio

Integration testing

Utilities for testing Apollo Server


uses a

to validate and execute incoming . 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 to ensure your request pipeline works as expected.

There are two main options for integration testing with :

  • Using ApolloServer's executeOperation method.
  • Setting up an HTTP client to your server.

Testing using executeOperation

's executeOperation method enables you to run through the request pipeline without sending an HTTP request.

The

accepts the following :

  • An object that describes the to execute.
    • This object must include a query specifying the to run. You can use executeOperation to execute both queries and , but both use the query .
  • An optional second object that is used as the 's
    contextValue
    ).

Below is a simplified example of setting up a test using the JavaScript testing library

:

index.test.ts
// 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
const typeDefs = `#graphql
type Query {
hello(name: String): String!
}
`;
const resolvers = {
Query: {
hello: (_, { name }) => `Hello ${name}!`,
},
};
it('returns hello with the provided name', async () => {
const testServer = new ApolloServer({
typeDefs,
resolvers,
});
const response = await testServer.executeOperation({
query: 'query SayHelloWorld($name: String) { hello(name: $name) }',
variables: { name: 'world' },
});
// Note the use of Node's assert rather than Jest's expect; if using
// TypeScript, `assert`` will appropriately narrow the type of `body`
// and `expect` will not.
assert(response.body.kind === 'single');
expect(response.body.singleResult.errors).toBeUndefined();
expect(response.body.singleResult.data?.hello).toBe('Hello world!');
});
index.test.js
// 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
const typeDefs = `#graphql
type Query {
hello(name: String): String!
}
`;
const resolvers = {
Query: {
hello: (_, { name }) => `Hello ${name}!`,
},
};
it('returns hello with the provided name', async () => {
const testServer = new ApolloServer({
typeDefs,
resolvers,
});
const response = await testServer.executeOperation({
query: 'query SayHelloWorld($name: String) { hello(name: $name) }',
variables: { name: 'world' },
});
// Note the use of Node's assert rather than Jest's expect; if using
// TypeScript, `assert`` will appropriately narrow the type of `body`
// and `expect` will not.
assert(response.body.kind === 'single');
expect(response.body.singleResult.errors).toBeUndefined();
expect(response.body.singleResult.data?.hello).toBe('Hello world!');
});

Note that when testing, any errors in parsing, validating, and executing your are returned in the nested errors of the result. As with any response, these errors are not thrown.

You don't need to start your server before calling executeOperation. The server instance will start automatically and throw any startup errors.

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.

integration.test.ts
it('fetches single launch', async () => {
const userAPI = new UserAPI({ store });
const launchAPI = new LaunchAPI();
// ensure our server's context is typed correctly
interface ContextValue {
user: User;
dataSources: {
userAPI: UserAPI;
launchAPI: LaunchAPI;
};
}
// create a test server to test against, using our production typeDefs,
// resolvers, and dataSources.
const server = new ApolloServer<ContextValue>({
typeDefs,
resolvers,
});
// mock the dataSource's underlying fetch methods
launchAPI.get = jest.fn(() => [mockLaunchResponse]);
userAPI.store = mockStore;
userAPI.store.trips.findAll.mockReturnValueOnce([
{ dataValues: { launchId: 1 } },
]);
// run the query against the server and snapshot the output
const res = await server.executeOperation(
{
query: GET_LAUNCH,
variables: { id: 1 },
},
{
contextValue: {
user: { id: 1, email: 'a@a.a' },
dataSources: {
userAPI,
launchAPI,
},
},
},
);
expect(res).toMatchSnapshot();
});

The example above includes a test-specific

, which provides data directly to the ApolloServer instance, bypassing any context initialization function you have.

If you want to test the behavior of your context function directly, we recommend running actual HTTP requests against your server.

For examples of both integration and end-to-end testing we recommend checking out the

.

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. doesn't provide built-in support for this at this time.

Instead, you can run against your server using a combination of any HTTP or such as

or
Apollo Client's HTTP Link
.

Below is an example of writing an end-to-end test using the @apollo/server and supertest packages:

server.test.ts
// we import a function that we wrote to create a new instance of Apollo Server
import { createApolloServer } from '../server';
// we'll use supertest to test our server
import request from 'supertest';
// this is the query for our test
const queryData = {
query: `query sayHello($name: String) {
hello(name: $name)
}`,
variables: { name: 'world' },
};
describe('e2e demo', () => {
let server, url;
// before the tests we spin up a new Apollo Server
beforeAll(async () => {
// Note we must wrap our object destructuring in parentheses because we already declared these variables
// We pass in the port as 0 to let the server pick its own ephemeral port for testing
({ server, url } = await createApolloServer({ port: 0 }));
});
// after the tests we'll stop the server
afterAll(async () => {
await server?.stop();
});
it('says hello', async () => {
// send our request to the url of the test server
const response = await request(url).post('/').send(queryData);
expect(response.errors).toBeUndefined();
expect(response.body.data?.hello).toBe('Hello world!');
});
});
server.test.js
// we import a function that we wrote to create a new instance of Apollo Server
import { createApolloServer } from '../server';
// we'll use supertest to test our server
import request from 'supertest';
// this is the query for our test
const queryData = {
query: `query sayHello($name: String) {
hello(name: $name)
}`,
variables: { name: 'world' },
};
describe('e2e demo', () => {
let server, url;
// before the tests we spin up a new Apollo Server
beforeAll(async () => {
// Note we must wrap our object destructuring in parentheses because we already declared these variables
// We pass in the port as 0 to let the server pick its own ephemeral port for testing
({ server, url } = await createApolloServer({ port: 0 }));
});
// after the tests we'll stop the server
afterAll(async () => {
await server?.stop();
});
it('says hello', async () => {
// send our request to the url of the test server
const response = await request(url).post('/').send(queryData);
expect(response.errors).toBeUndefined();
expect(response.body.data?.hello).toBe('Hello world!');
});
});

You can also view and fork this complete example on CodeSandbox:

Edit integration-testing

Previous
Mocking
Next
Caching
Edit on GitHubEditForumsDiscord

© 2024 Apollo Graph Inc.

Privacy Policy

Company