-
Notifications
You must be signed in to change notification settings - Fork 2k
docs: testing graphQL servers #4374
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sarahxsanders
wants to merge
9
commits into
graphql:16.x.x
Choose a base branch
from
sarahxsanders:testing-graphql-servers
base: 16.x.x
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
4fb470d
add testing graphQL servers docs
sarahxsanders ec58803
proofreading and typo cleanup
sarahxsanders 989a0dd
more proofreading, adding links, and reorganizing the left-hand nav
sarahxsanders 5275533
Merge branch '16.x.x' into testing-graphql-servers
sarahxsanders 7060685
fix spellcheck
sarahxsanders 036bc2b
Merge branch '16.x.x' into testing-graphql-servers
benjie 131f709
Apply suggestions from code review
benjie cd00ddf
Fix various formatting issues
benjie bac600c
Merge branch '16.x.x' into testing-graphql-servers
sarahxsanders File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
--- | ||
title: Testing Approaches | ||
sidebarTitle: Testing Approaches | ||
--- | ||
|
||
import { Callout } from 'nextra/components' | ||
|
||
# Testing Approaches | ||
|
||
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 | ||
|
||
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: | ||
|
||
- Receives input arguments and context as expected | ||
- Calls the correct business logic | ||
- Handles errors properly | ||
- Returns the correct result | ||
|
||
### When to use unit tests | ||
|
||
Unit tests are fast and provide tight feedback loops. They're especially useful when: | ||
|
||
- Developing new resolvers | ||
- Validating error handling and edge cases | ||
- Refactoring resolver logic and needing immediate verification | ||
|
||
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. | ||
|
||
### Example: Unit test for a resolver | ||
|
||
This test verifies that the resolver produces the expected result using mocked inputs. | ||
|
||
```javascript | ||
const result = await myResolver(parent, args, context); | ||
expect(result).toEqual(expectedResult); | ||
``` | ||
|
||
## Integration tests | ||
|
||
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: | ||
|
||
- The schema is correctly wired to resolvers | ||
- Resolvers behave as expected when called through a query | ||
- Data sources return expected results | ||
|
||
### When to use integration tests | ||
|
||
Use integration tests when: | ||
|
||
- You want to test the full operation flow from request to result | ||
- You're testing how resolvers handle variables, fragments, and nested fields | ||
- You want higher confidence that your schema and resolvers work together | ||
|
||
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: | ||
|
||
- Confirms schema and resolver wiring | ||
- Higher confidence than unit tests alone | ||
- Requires more setup | ||
- May miss production-specific issues such as network transport errors | ||
|
||
<Callout type="default"> | ||
|
||
If you're preparing to onboard frontend clients or exposing your API to consumers, | ||
integration tests catch mismatches early before they reach production. | ||
|
||
</Callout> | ||
|
||
### Example: Integration test with `graphql()` | ||
|
||
This test validates a user query with variables and mocked context. | ||
|
||
```js | ||
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); | ||
``` | ||
|
||
## End-to-End (E2E) tests | ||
|
||
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: | ||
|
||
- HTTP transport | ||
- Middleware such as authentication and logging | ||
- Real data sources | ||
- Infrastructure including networking and caching | ||
|
||
E2E tests simulate production-like conditions and are especially valuable when: | ||
|
||
- You're testing critical user flows end to end | ||
- You want to validate authentication and authorization | ||
- You need to test network-level behaviors such as timeouts and error handling | ||
- You're coordinating multiple services together | ||
|
||
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: | ||
|
||
- Validates the full system in realistic conditions | ||
- Catches issues unit and integration tests might miss | ||
- Slower and resource-intensive | ||
- Requires infrastructure setup | ||
|
||
<Callout type="info"> | ||
|
||
In the following guides, we focus on unit and integration tests. E2E tests are | ||
valuable, but they require different tooling and workflows. | ||
|
||
</Callout> | ||
|
||
## Comparing unit tests and integration tests | ||
|
||
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. | ||
|
||
## Choose a testing approach | ||
|
||
There is no single correct approach to testing. Instead, a layered approach | ||
works best. In general: | ||
|
||
- Start with unit tests to move quickly and catch logic errors early | ||
- Add integration tests to ensure schema and resolver wiring is correct | ||
- Use E2E tests sparingly for high-confidence checks on critical flows | ||
|
||
The goal is to build a safety net of tests that gives you fast feedback during | ||
development and high confidence in production. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
--- | ||
title: Testing Best Practices | ||
sidebarTitle: Testing Best Practices | ||
--- | ||
|
||
# Testing Best Practices | ||
|
||
As your GraphQL server grows, so does the risk of regressions, inconsistencies, | ||
and slow development feedback. A thoughtful testing strategy helps you catch | ||
problems early and ship with confidence—without overwhelming your team with | ||
redundant or brittle tests. | ||
|
||
This guide outlines practical testing patterns for GraphQL servers, | ||
including schema safety, test coverage, data management, performance, and | ||
continuous integration. | ||
|
||
## Schema stability | ||
|
||
Your schema is a contract with every client and consumer. Changes should | ||
be intentional and reviewed. | ||
|
||
### Best practices | ||
|
||
- Use snapshot tests to catch unintended schema changes | ||
- Tool: [`jest-serializer-graphql-schema`](https://www.npmjs.com/package/jest-serializer-graphql-schema) | ||
- Example: | ||
```ts | ||
expect(schema).toMatchSnapshot(); | ||
``` | ||
- Consider sorting the schema to ensure a stable order of your schemas types, fields and arguments in the snapshots. | ||
```ts | ||
expect(lexicographicSortSchema(schema)).toMatchSnapshot(); | ||
``` | ||
- Use schema diff tools in CI: | ||
- `graphql-inspector` | ||
- Apollo Rover | ||
- GraphQL Hive | ||
- Require review or approval for breaking changes | ||
- Treat schema changes like semver: breaking changes should be explicit and | ||
intentional | ||
|
||
## Focus test coverage | ||
|
||
You don’t need 100% coverage, you need meaningful coverage. Prioritize | ||
behavior that matters. | ||
|
||
### Best practices | ||
|
||
- Test high-value paths: | ||
- Business logic and custom resolver behavior | ||
- Error cases: invalid input, auth failures, fallback logic | ||
- Nullable fields and partial success cases | ||
- Integration between fields, arguments, and data dependencies | ||
- Coverage strategy: | ||
- Unit test resolvers with significant logic | ||
- Integration test operations end-to-end | ||
- Avoid duplicating tests across layers | ||
- Use tools to identify gaps: | ||
- `graphql-coverage` | ||
- Jest `--coverage` | ||
- Static analysis for unused fields/resolvers | ||
|
||
## Managing test data | ||
|
||
Clean, flexible test data makes your tests easier to write, read, and | ||
maintain. | ||
|
||
### Best practices | ||
|
||
- Use factories instead of hardcoding: | ||
|
||
```ts | ||
function createUser(overrides = {}) { | ||
return { id: '1', name: 'Test User', ...overrides }; | ||
} | ||
benjie marked this conversation as resolved.
Show resolved
Hide resolved
|
||
``` | ||
|
||
- Share fixtures: | ||
|
||
```ts | ||
export function createTestContext(overrides = {}) { | ||
return { | ||
db: { findUser: jest.fn() }, | ||
...overrides, | ||
}; | ||
} | ||
``` | ||
|
||
- Keep test data small and expressive | ||
- Avoid coupling test data to real database structures | ||
unless explicitly testing integration | ||
|
||
## Keep tests fast and isolated | ||
|
||
Slow tests kill iteration speed. Fast tests build confidence. | ||
|
||
To keep tests lean: | ||
|
||
- Use `graphql()` instead of spinning up a server | ||
- Use in-memory or mock data—avoid real databases in most tests | ||
- Group tests by concern: resolver, operation, schema | ||
- Use parallelization (e.g., Jest, Vitest) | ||
- Avoid shared state or global mocks that leak across test files | ||
|
||
For large test suites: | ||
|
||
- Split tests by service or domain | ||
- Cache expensive steps where possible | ||
|
||
## Integrate tests into CI | ||
|
||
Tests are only useful if they run consistently and early. | ||
|
||
### Best practices | ||
|
||
- Run tests on every push or PR: | ||
- Lint GraphQL files and scalars | ||
- Run resolver and operation tests | ||
- Validate schema via snapshot or diff | ||
- Fail fast: | ||
- Break the build on schema snapshot diffs | ||
- Block breaking changes without a migration plan | ||
- Use GitHub annotations or reporters to surface failures in PRs | ||
|
||
## Lint your schema | ||
|
||
Testing behavior is only the start. Clean, consistent schemas are | ||
easier to maintain and consume. | ||
|
||
Use schema linting to enforce: | ||
|
||
- Descriptions on public fields and types | ||
- Consistent naming and casing | ||
- Deprecation patterns | ||
- Nullability rules | ||
|
||
Tools: | ||
|
||
- `graphql-schema-linter` | ||
- `@graphql-eslint/eslint-plugin` |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.