|
| 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. |
0 commit comments