Skip to content

Commit df6e377

Browse files
committed
add testing graphQL servers docs
1 parent 0a848cc commit df6e377

5 files changed

+720
-0
lines changed
+167
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
---
2+
title: Testing Approaches
3+
sidebarTitle: Testing Approaches
4+
---
5+
6+
# Testing Approaches
7+
8+
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.
9+
10+
## Unit tests
11+
12+
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.
13+
14+
When you write unit tests, you’re checking that the resolver:
15+
- Receives input arguments and context as expected
16+
- Calls the correct business logic
17+
- Handles errors properly
18+
- Returns the correct result
19+
20+
Unit tests are fast and provide tight feedback loops. They're especially useful when:
21+
- Developing new resolvers
22+
- Validating error handling and edge cases
23+
- Refactoring resolver logic and needing immediate verification
24+
25+
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.
26+
27+
### Example: Unit test for a resolver
28+
29+
This test verifies that the resolver produces the expected result, using mocked inputs.
30+
31+
```javascript
32+
const result = await myResolver(parent, args, context);
33+
expect(result).toEqual(expectedResult);
34+
```
35+
36+
## Integration tests
37+
38+
Integration tests go a step further by testing resolvers and the schema together.
39+
You can run actual GraphQL queries and verify the end-to-end behavior within your
40+
appliaction's boundaries.
41+
42+
You can use the `graphql()` function from the GraphQL pacakage, no HTTP server
43+
needed. With the `graphql()` function, you can test the full flow: query > schema >
44+
resolver > data source (mocked or real).
45+
46+
With integration tests, you are verifiying that:
47+
48+
- The schema is correctly wired to resolvers
49+
- Resolvers behave as expected when called through a query
50+
- Data sources return expected results
51+
52+
### When to use integration tests
53+
54+
Integration tests are valuable when:
55+
56+
- You want to test the full operation flow from query to result
57+
- You're testing how resolvers handle variables, fragments, and nested queries
58+
- You want higher confidence that your schema and resolvers work together
59+
60+
Integration tests are slightly slower than unit tests but still fast enough for
61+
regular development cycles, especially when you mock external data sources.
62+
63+
Trade-offs to consider:
64+
65+
- Confirms schema and resolver wiring
66+
- Higher confidence than unit tests alone
67+
- Requires more setup
68+
- May miss production-specific issues such as network transport errors
69+
70+
> [!TIP]
71+
>
72+
> If you're preparing to onboard frontend clients or exposing your API to consumers,
73+
> integration tests catch mismatches early before they reach production.
74+
75+
### Example: Integration test with `graphql()`
76+
77+
This test validates that your schema, resolver, and data sources work together
78+
as expected:
79+
80+
```js
81+
const query = `
82+
query GetUser($id: ID!) {
83+
user(id: $id) {
84+
id
85+
name
86+
}
87+
}
88+
`;
89+
90+
const result = await graphql({
91+
schema,
92+
source: query,
93+
variableValues: { id: '123' },
94+
contextValue: mockedContext,
95+
});
96+
97+
expect(result.data).toEqual(expectedData);
98+
```
99+
100+
## End-to-End (E2E) tests
101+
102+
E2E tests exercise the entire stack. With your server running and real HTTP
103+
requests in play, you validate not just schema and resolver behvaior, but also:
104+
105+
- HTTP transport
106+
- Middleware such as authentication and logging
107+
- Real data sources
108+
- Infrastructure including networking and caching
109+
110+
E2E tests simulate production-like conditions and are especially valuable when:
111+
112+
- You're testing critical user flows end to end
113+
- You want to validate authentication and authorization
114+
- You need to test network-level behaviors such as timeouts and error handling
115+
- You're coordinating multiple services together
116+
117+
E2E tests offer high confidence but come at the cost of speed and complexity.
118+
They’re best used sparingly for critical paths, not as your primary testing approach.
119+
120+
Trade-offs to consider:
121+
122+
- Validates the full system in realistic conditions
123+
- Catches issues unit and integration tests might miss
124+
- Slower and resource-intensive
125+
- Requires infrastructure setup
126+
127+
> [!NOTE]
128+
>
129+
> In the following guides, we focus on unit and integration tests. E2E tests are
130+
> valuable, but they require different tooling and workflows.
131+
132+
## Comparing unit tests and integration tests
133+
134+
Unit and integration tests are complementary, not competing.
135+
136+
| Factor | Unit tests | Integration tests |
137+
|:-------|:--------------------|:-------------------------|
138+
| Speed | Fast | Moderate |
139+
| Scope | Resolver logic only | Schema and resolver flow |
140+
| Setup | Minimal | Schema, mocks, context |
141+
| Best for | Isolated business logic, fast development loops | Verifying resolver wiring, operation flow |
142+
143+
When starting a new project or feature, use unit tests to validate resolver
144+
logic quickly. As your schema grows and you onboard consumers, add integration
145+
tests to confirm that everything works end to end within your application.
146+
147+
## When E2E tests add value
148+
149+
E2E tests are slower and require a full environment setup, but they are essential when:
150+
- Testing authentication and authorization flows
151+
- Validating infrastructure behavior such as networking and retries
152+
- Coordinating between multiple services and APIs
153+
154+
Think of E2E tests as final rehearsals for your API. They’re not your first
155+
line of defense, but they provide high confidence before deploying to production.
156+
157+
## Choosing the right balance
158+
159+
There is no one-size-fits-all approach to testing. Instead, a layered approach
160+
works best. In general:
161+
162+
- Start with unit tests to move quickly and catch logic errors early
163+
- Add integration tests to ensure scehma and resolver wiring is correct
164+
- Use E2E tests sparingly for high-confidence checks on critical flows
165+
166+
The goal is to build a safety net of tests that gives you fast feedback during
167+
development and high confidence in production.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
---
2+
title: Testing Best Practices
3+
sidebarTitle: Testing Best Practices
4+
---
5+
6+
# Testing Best Practices
7+
8+
As your GraphQL server grows, so does the risk of regressions, inconsistencies,
9+
and slow development feedback. A thoughtful testing strategy helps you catch
10+
problems early and ship with confidence—without overwhelming your team with
11+
redundant or brittle tests.
12+
13+
This guide outlines practical testing patterns for GraphQL servers,
14+
including schema safety, test coverage, data management, performance, and
15+
continuous integration.
16+
17+
## Schema stability
18+
19+
Your schema is a contract with every client and consumer. Changes should
20+
be intentional and reviewed.
21+
22+
### Best practices
23+
24+
- Use snapshot tests to catch unintended schema changes
25+
– Tool: [`jest-serializer-graphql-schema`](https://github.com/kamilkisiela/jest-serializer-graphql-schema)
26+
– Example:
27+
```ts
28+
expect(printSchema(schema)).toMatchSnapshot();
29+
```
30+
- Use schema diff tools in CI:
31+
- `graphql-inspector`
32+
- Apollo Rover
33+
- GraphQL Hive
34+
- Require review or approval for breaking changes
35+
- Treat schema changes like semver: breaking changes should be explicit and
36+
intentional
37+
38+
## Focus test coverage
39+
40+
You don’t need 100% coverage, you need meaningful coverage. Prioritize
41+
behavior that matters.
42+
43+
### Best practices
44+
45+
- Test high-value paths:
46+
- Business logic and custom resolver behavior
47+
- Error cases: invalid input, auth failures, fallback logic
48+
- Nullable fields and partial success cases
49+
- Integration between fields, arguments, and data dependencies
50+
- Coverage strategy:
51+
- Unit test resolvers with significant logic
52+
- Integration test operations end-to-end
53+
- Avoid duplicating tests across layers
54+
- Use tools to identify gaps:
55+
- `graphql-coverage`
56+
- Jest `--coverage`
57+
- Static analysis for unused fields/resolvers
58+
59+
## Managing test data
60+
61+
Clean, flexible test data makes your tests easier to write, read, and
62+
maintain.
63+
64+
### Best practices
65+
66+
- Use factories instead of hardcoding:
67+
```ts
68+
function createUser(overrides = {}) {
69+
return { id: '1', name: 'Test User', ...overrides };
70+
}
71+
- Share fixtures:
72+
```ts
73+
export function createTestContext(overrides = {}) {
74+
return {
75+
db: { findUser: jest.fn() },
76+
...overrides,
77+
};
78+
}
79+
```
80+
- Keep test data small and expressive.
81+
- Avoid coupling test data to real database structures
82+
unless explicitly testing integration.
83+
84+
## Keep test fast and isolated
85+
86+
Slow tests kill iteration speed. Fast tests build confidence.
87+
88+
To keep tests lean:
89+
- Use `graphql()` instead of spinning up a server
90+
- Use in-memory or mock dataavoid real databases in most tests
91+
- Group tests by concern: resolver, operation, schema
92+
- Use parallelization (e.g., Jest, Vitest)
93+
- Avoid shared state or global mocks that leak across test files
94+
95+
For large test suites:
96+
- Split tests by service or domain
97+
- Cache expensive steps where possible
98+
99+
## Integrate tests into CI
100+
101+
Tests are only useful if they run consistently and early.
102+
103+
### Best practices
104+
105+
- Run tests on every push or PR:
106+
- Lint GraphQL files and scalars
107+
- Run resolver and operation tests
108+
- Validate schema via snapshot or diff
109+
- Fail fast:
110+
- Break the build on schema snapshot diffs
111+
- Block breaking changes without a migration plan
112+
- Use GitHub annotations or reporters to surface failures in PRs
113+
114+
## Lint your schema
115+
116+
Testing behavior is only half the battleclean, consistent schemas are
117+
easier to maintain and consume.
118+
119+
Use schema linting to enforce:
120+
- Descriptions on public fields and types
121+
- Consistent naming and casing
122+
- Deprecation patterns
123+
- Nullability rules
124+
125+
Tools:
126+
- `graphql-schema-linter`
127+
- `eslint-plugin-graphql`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
title: Testing GraphQL Servers
3+
sidebarTitle: Testing GraphQL Servers
4+
---
5+
6+
import { Tabs } from 'nextra/components';
7+
8+
# Testing GraphQL Servers
9+
10+
## Why testing matters
11+
12+
GraphQL's type system provides strong safety guarantees, but it
13+
all reliable APIs need testing. The GraphQL compiler and runtime
14+
enforce type correctness, and schema introspection helps clients
15+
understand valid queries.
16+
17+
What GraphQL can't protect you from is:
18+
19+
- Databases returning incorrect data
20+
- Resolvers throwing unexpected errors
21+
- Integrations returning unexpected `null` values
22+
- Schema changes breaking client applications
23+
24+
A robust testing strategy helps you scale and maintain your API in production.
25+
Combining static and dynamic tests gives you confidence that your GraphQL server
26+
behaves as expected.
27+
28+
## Risks of schema-first development
29+
30+
Schema-first development starts with designing your API upfront and letting
31+
the schema drive implementation. This is a solid foundation, but treating
32+
the schema as the only source of truth creates risks as your API evolves.
33+
34+
For example, if you rename a field, any client still expecting the original name
35+
will break. Changing a field's type (like from `String` to `Int`) can also cause
36+
unexpected failures in downstream clients. While GraphQL tooling, like schema
37+
validation, helps enforce structure, it won't stop you from making breaking changes.
38+
39+
Schema changes may feel safe, but clients depend on that schema to remain
40+
stable over time. Without tests or tracking, these changes can quickly go
41+
unnoticed.
42+
43+
By testing schema changes and integrating schema tracking into your workflow,
44+
you can catch breaking changes before they reach production. A strong testing strategy treats your schema as part of your system's contract.
45+
46+
## Common resolver issues
47+
48+
A correct schema doesn't guarantee safe execution. Resolvers are still a risk surface. Resolvers connect the schema to your business logic and data sources. They decide how data is fetched, transformed, and returned to clients.
49+
50+
Resolver errors are dangerous because failures often return `null` in part of the response, without failing the entire operation. Errors at the resolver level
51+
don't necessarily break the whole response. Clients may receive incomplete data without realizing something went wrong. Tests should assert on complete and correct responses, not just that there was a response.
52+
53+
Unit tests of resolver ensure:
54+
55+
- Resolvers pass the correct inputs to your business logic.
56+
- Resolvers return the expected outputs to the schema.
57+
- Errors are surfaced clearly and handled predictably.
58+
59+
## Handling external dependencies
60+
61+
GraphQL servers often feel like the source of truth, but they're rarely the system of record. Your server talks to databases, internal APIs, and external third-party services.
62+
63+
External dependencies add complexity and risk. Even with a correct schema and resolvers, failures in upstream systems can disrupt your API. For
64+
example:
65+
66+
- An unavailable database can cause the resolver to fail.
67+
- A slow third-party API can lead to timeouts.
68+
- An external service returning incomplete data can result in `null` values or
69+
errors in your response.
70+
71+
APIs should fail in predictable ways. Good tests don't just check happy paths, they simulate timeouts, network failures, corrupted data, and empty responses. Building these scenarios into your testing strategy helps you catch issues early and keep your API reliable.
72+
73+
Beyond simulating failures, consider testing resilience patterns like retries or circuit breakers. These strategies help your API recover from transient failures and prevent cascading issues, especially in production environments.
74+
75+
## Next steps
76+
77+
- Learn different types of tests for GraphQL servers to choose the right strategy for your project.
78+
- Explore how to test operations without running a server.
79+
- Understand how to unit test resolvers to catch logic errors early.
80+
- Apply best practices to scale testing to production.

0 commit comments

Comments
 (0)