patterntypescriptgraphqlModerate
Testing GraphQL resolvers — unit test services, integration test via executeOperation
Viewed 0 times
@apollo/server 4.x, vitest or jest
testingexecuteOperationresolver testintegration testunit testmocking context
Problem
Testing resolvers via HTTP requires running a full server, managing ports, and dealing with auth setup. Testing resolvers in isolation misses schema validation and context behavior. Neither extreme is ideal.
Solution
Unit test service functions independently. Integration test via Apollo Server's
executeOperation which bypasses HTTP.// Unit test — service function directly
describe('UserService.createUser', () => {
it('throws when email exists', async () => {
mockDb.user.findFirst.mockResolvedValue(existingUser);
await expect(service.createUser({ email: 'x@x.com', name: 'X' })).rejects.toThrow('Email already in use');
});
});
// Integration test — via executeOperation (no HTTP)
import { ApolloServer } from '@apollo/server';
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
it('returns user by id', async () => {
const { body } = await server.executeOperation(
{ query: GET_USER, variables: { id: '1' } },
{ contextValue: { user: mockUser, db: testDb, loaders: createLoaders() } }
);
expect(body.kind).toBe('single');
expect(body.singleResult.data?.user.email).toBe('test@example.com');
});Why
executeOperation runs the full GraphQL pipeline (parse, validate, execute) without HTTP overhead. Context is injected directly, so auth can be mocked cleanly. It's much faster than supertest HTTP tests.Gotchas
- Call
await server.start()beforeexecuteOperationin test setup - Call
await server.stop()in afterAll to clean up - Check
body.kind === 'single'before accessingbody.singleResult— incremental results have kind 'incremental' - Mock DataLoaders in context — real loaders may batch across test cases if not recreated per test
Context
Writing tests for a GraphQL server with Apollo Server
Revisions (0)
No revisions yet.