Testing GraphQL APIs
1. Setting Up Test Environment
| Tool | Use |
|---|---|
| Vitest / Jest | Test runner |
| graphql / @graphql-tools/executor | In-process execution |
| Testcontainers | Real DB in CI |
| MSW | Mock HTTP responses for client tests |
2. Testing Queries
Example: In-process query test
import { graphql } from "graphql";
import { schema } from "../src/schema";
test("user query returns name", async () => {
const result = await graphql({
schema,
source: `query U($id: ID!) { user(id: $id) { name } }`,
variableValues: { id: "1" },
contextValue: { db: testDb, user: testUser }
});
expect(result.errors).toBeUndefined();
expect(result.data.user.name).toBe("Ada");
});
3. Testing Mutations
Example: Mutation test
test("createPost", async () => {
const r = await graphql({ schema, source: CREATE_POST, variableValues: { input }, contextValue });
expect(r.data.createPost.post.id).toBeDefined();
expect(await testDb.post.count()).toBe(1);
});
4. Testing Subscriptions
Example: Subscribe test
import { subscribe, parse } from "graphql";
const iter = await subscribe({ schema, document: parse(SUB_QUERY), contextValue });
pubsub.publish("EVENT", { messageAdded: { id: "1", body: "hi" } });
const { value } = await iter.next();
expect(value.data.messageAdded.body).toBe("hi");
5. Testing Resolvers
| Strategy | Detail |
|---|---|
| Unit (call resolver directly) | Fast, isolated |
| Integration (via graphql()) | Tests selection + parse + validate |
| E2E (HTTP) | Tests middleware, auth, transport |
6. Mocking Data Sources
Example: Schema mocking
import { addMocksToSchema } from "@graphql-tools/mock";
const mocked = addMocksToSchema({
schema,
mocks: { User: () => ({ name: "Mock", email: "m@x.co" }) }
});
7. Testing Error Handling
| Assertion | Detail |
|---|---|
| errors[0].message | Verify content (or absence) |
| errors[0].extensions.code | Code-level assertion |
| errors[0].path | Where it occurred |
| data preserved | Partial-data behavior |
8. Testing Authentication
Example: Auth scenarios
test.each([
["anonymous", null, "UNAUTHENTICATED"],
["non-admin", { id: "u1", role: "USER" }, "FORBIDDEN"],
["admin", { id: "u2", role: "ADMIN" }, undefined]
])("admin op as %s", async (_, user, code) => {
const r = await graphql({ schema, source: ADMIN_OP, contextValue: { user } });
expect(r.errors?.[0]?.extensions.code).toBe(code);
});
9. Testing Authorization
| Pattern | Detail |
|---|---|
| Negative test | Verify forbidden access throws |
| Positive test | Verify owner/admin succeeds |
| Field-level | Verify masked fields return null |
10. Using Schema Testing
| Tool | Detail |
|---|---|
| graphql-inspector | Diff old vs new for breaking changes |
| graphql-eslint | Style + best practices |
| Apollo schema check | Validate against operation traffic |
11. Testing Performance
| Tool | Use |
|---|---|
| k6 / Artillery | Load test against /graphql |
| Apollo tracing | Per-resolver duration |
| DataLoader assertions | Count batch loads in tests |