|
| 1 | +--- |
| 2 | +title: Testing Approaches |
| 3 | +sidebarTitle: Testing Approaches |
| 4 | +--- |
| 5 | + |
| 6 | +import { Callout } from 'nextra/components' |
| 7 | + |
| 8 | +# Testing Approaches |
| 9 | + |
| 10 | +Testing is essential for building reliable GraphQL servers. But not every test |
| 11 | +gives you the same kind of feedback, and not every test belongs at every stage of development. |
| 12 | +This guide explains the differences between unit tests, integration tests, and |
| 13 | +end-to-end (E2E) tests, so you can choose the right approach for your project. |
| 14 | + |
| 15 | +## Unit tests |
| 16 | + |
| 17 | +Unit tests focus on testing resolver functions in isolation. You run the resolver directly |
| 18 | +with mocked arguments and context. These tests do not involve your schema or run actual |
| 19 | +GraphQL queries. |
| 20 | + |
| 21 | +When you write unit tests, you’re checking that the resolver: |
| 22 | + |
| 23 | +- Receives input arguments and context as expected |
| 24 | +- Calls the correct business logic |
| 25 | +- Handles errors properly |
| 26 | +- Returns the correct result |
| 27 | + |
| 28 | +### When to use unit tests |
| 29 | + |
| 30 | +Unit tests are fast and provide tight feedback loops. They're especially useful when: |
| 31 | + |
| 32 | +- Developing new resolvers |
| 33 | +- Validating error handling and edge cases |
| 34 | +- Refactoring resolver logic and needing immediate verification |
| 35 | + |
| 36 | +However, unit tests have limitations. Because you're mocking inputs and skipping the schema |
| 37 | +entirely, you won’t catch issues with how your schema connects to your resolver functions. |
| 38 | +There's also a risk of false positives if your mocks drift from real usage over time. |
| 39 | + |
| 40 | +### Example: Unit test for a resolver |
| 41 | + |
| 42 | +This test verifies that the resolver produces the expected result using mocked inputs. |
| 43 | + |
| 44 | +```javascript |
| 45 | +const result = await myResolver(parent, args, context); |
| 46 | +expect(result).toEqual(expectedResult); |
| 47 | +``` |
| 48 | + |
| 49 | +## Integration tests |
| 50 | + |
| 51 | +Integration tests go a step further by testing resolvers and the schema together. |
| 52 | +You can run actual GraphQL queries and verify the end-to-end behavior within your |
| 53 | +application's boundaries. |
| 54 | + |
| 55 | +You can use the `graphql()` function from the GraphQL package, no HTTP server |
| 56 | +needed. With the `graphql()` function, you can test the full flow: request > schema > |
| 57 | +resolver > data source (mocked or real). |
| 58 | + |
| 59 | +Integration tests confirm that: |
| 60 | + |
| 61 | +- The schema is correctly wired to resolvers |
| 62 | +- Resolvers behave as expected when called through a query |
| 63 | +- Data sources return expected results |
| 64 | + |
| 65 | +### When to use integration tests |
| 66 | + |
| 67 | +Use integration tests when: |
| 68 | + |
| 69 | +- You want to test the full operation flow from request to result |
| 70 | +- You're testing how resolvers handle variables, fragments, and nested fields |
| 71 | +- You want higher confidence that your schema and resolvers work together |
| 72 | + |
| 73 | +Integration tests are slightly slower than unit tests but still fast enough for |
| 74 | +regular development cycles, especially when you mock external data sources. |
| 75 | + |
| 76 | +Trade-offs to consider: |
| 77 | + |
| 78 | +- Confirms schema and resolver wiring |
| 79 | +- Higher confidence than unit tests alone |
| 80 | +- Requires more setup |
| 81 | +- May miss production-specific issues such as network transport errors |
| 82 | + |
| 83 | +<Callout type="default"> |
| 84 | + |
| 85 | + If you're preparing to onboard frontend clients or exposing your API to consumers, |
| 86 | + integration tests catch mismatches early before they reach production. |
| 87 | + |
| 88 | +</Callout> |
| 89 | + |
| 90 | +### Example: Integration test with `graphql()` |
| 91 | + |
| 92 | +This test validates a user query with variables and mocked context. |
| 93 | + |
| 94 | +```js |
| 95 | +const query = ` |
| 96 | + query GetUser($id: ID!) { |
| 97 | + user(id: $id) { |
| 98 | + id |
| 99 | + name |
| 100 | + } |
| 101 | + } |
| 102 | +`; |
| 103 | + |
| 104 | +const result = await graphql({ |
| 105 | + schema, |
| 106 | + source: query, |
| 107 | + variableValues: { id: '123' }, |
| 108 | + contextValue: mockedContext, // mock database, authorization, loaders, etc. |
| 109 | +}); |
| 110 | + |
| 111 | +expect(result.data).toEqual(expectedData); |
| 112 | +``` |
| 113 | + |
| 114 | +## End-to-End (E2E) tests |
| 115 | + |
| 116 | +E2E tests exercise the entire stack. With your server running and real HTTP |
| 117 | +requests in play, you validate not just schema and resolver behavior, but also: |
| 118 | + |
| 119 | +- HTTP transport |
| 120 | +- Middleware such as authentication and logging |
| 121 | +- Real data sources |
| 122 | +- Infrastructure including networking and caching |
| 123 | + |
| 124 | +E2E tests simulate production-like conditions and are especially valuable when: |
| 125 | + |
| 126 | +- You're testing critical user flows end to end |
| 127 | +- You want to validate authentication and authorization |
| 128 | +- You need to test network-level behaviors such as timeouts and error handling |
| 129 | +- You're coordinating multiple services together |
| 130 | + |
| 131 | +E2E tests offer high confidence but come at the cost of speed and complexity. |
| 132 | +They’re best used sparingly for critical paths, not as your primary testing approach. |
| 133 | + |
| 134 | +Trade-offs to consider: |
| 135 | + |
| 136 | +- Validates the full system in realistic conditions |
| 137 | +- Catches issues unit and integration tests might miss |
| 138 | +- Slower and resource-intensive |
| 139 | +- Requires infrastructure setup |
| 140 | + |
| 141 | +<Callout type="info"> |
| 142 | + |
| 143 | + In the following guides, we focus on unit and integration tests. E2E tests are |
| 144 | + valuable, but they require different tooling and workflows. |
| 145 | + |
| 146 | +</Callout> |
| 147 | + |
| 148 | +## Comparing unit tests and integration tests |
| 149 | + |
| 150 | +Unit and integration tests are complementary, not competing. |
| 151 | + |
| 152 | +| Factor | Unit tests | Integration tests | |
| 153 | +|:-------|:--------------------|:-------------------------| |
| 154 | +| Speed | Fast | Moderate | |
| 155 | +| Scope | Resolver logic only | Schema and resolver flow | |
| 156 | +| Setup | Minimal | Schema, mocks, context | |
| 157 | +| Best for | Isolated business logic, fast development loops | Verifying resolver wiring, operation flow | |
| 158 | + |
| 159 | +Start with unit tests when building new features, then layer in integration tests |
| 160 | +to validate schema wiring and catch regressions as your API grows. |
| 161 | + |
| 162 | +## Choose a testing approach |
| 163 | + |
| 164 | +There is no single correct approach to testing. Instead, a layered approach |
| 165 | +works best. In general: |
| 166 | + |
| 167 | +- Start with unit tests to move quickly and catch logic errors early |
| 168 | +- Add integration tests to ensure schema and resolver wiring is correct |
| 169 | +- Use E2E tests sparingly for high-confidence checks on critical flows |
| 170 | + |
| 171 | +The goal is to build a safety net of tests that gives you fast feedback during |
| 172 | +development and high confidence in production. |
0 commit comments