website/pages/docs/testing-approaches.mdx
import { Callout } from 'nextra/components'
Testing is essential for building reliable GraphQL servers. But not every test gives you the same kind of feedback, and not every test belongs at every stage of development. This guide explains the differences between unit tests, integration tests, and end-to-end (E2E) tests, so you can choose the right approach for your project.
Unit tests focus on testing resolver functions in isolation. You run the resolver directly with mocked arguments and context. These tests do not involve your schema or run actual GraphQL queries.
When you write unit tests, you’re checking that the resolver:
Unit tests are fast and provide tight feedback loops. They're especially useful when:
However, unit tests have limitations. Because you're mocking inputs and skipping the schema entirely, you won’t catch issues with how your schema connects to your resolver functions. There's also a risk of false positives if your mocks drift from real usage over time.
This test verifies that the resolver produces the expected result using mocked inputs.
const result = await myResolver(parent, args, context);
expect(result).toEqual(expectedResult);
Integration tests go a step further by testing resolvers and the schema together. You can run actual GraphQL queries and verify the end-to-end behavior within your application's boundaries.
You can use the graphql() function from the GraphQL package, no HTTP server
needed. With the graphql() function, you can test the full flow: request > schema >
resolver > data source (mocked or real).
Integration tests confirm that:
Use integration tests when:
Integration tests are slightly slower than unit tests but still fast enough for regular development cycles, especially when you mock external data sources.
Trade-offs to consider:
If you're preparing to onboard frontend clients or exposing your API to consumers, integration tests catch mismatches early before they reach production.
</Callout>graphql()This test validates a user query with variables and mocked context.
const query = `
query GetUser($id: ID!) {
user(id: $id) {
id
name
}
}
`;
const result = await graphql({
schema,
source: query,
variableValues: { id: '123' },
contextValue: mockedContext, // mock database, authorization, loaders, etc.
});
expect(result.data).toEqual(expectedData);
E2E tests exercise the entire stack. With your server running and real HTTP requests in play, you validate not just schema and resolver behavior, but also:
E2E tests simulate production-like conditions and are especially valuable when:
E2E tests offer high confidence but come at the cost of speed and complexity. They’re best used sparingly for critical paths, not as your primary testing approach.
Trade-offs to consider:
In the following guides, we focus on unit and integration tests. E2E tests are valuable, but they require different tooling and workflows.
</Callout>Unit and integration tests are complementary, not competing.
| Factor | Unit tests | Integration tests |
|---|---|---|
| Speed | Fast | Moderate |
| Scope | Resolver logic only | Schema and resolver flow |
| Setup | Minimal | Schema, mocks, context |
| Best for | Isolated business logic, fast development loops | Verifying resolver wiring, operation flow |
Start with unit tests when building new features, then layer in integration tests to validate schema wiring and catch regressions as your API grows.
There is no single correct approach to testing. Instead, a layered approach works best. In general:
The goal is to build a safety net of tests that gives you fast feedback during development and high confidence in production.