Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
31 Oct 2025 · 6 min read ·Article 123 / 125
Go

123 Testing gqlgen Resolvers with `httptest` and Mocks

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

123 Testing gqlgen Resolvers with httptest and Mocks

GraphQL is growing increasingly popular in modern backend ecosystems. One of the main reasons is the flexibility and efficiency of its queries. In Golang, gqlgen offers powerful tools for managing a GraphQL server, one of them being generating resolvers from a schema. But without good testing, we risk introducing bugs and unexpected behavior. So, what is an effective way to test GraphQL resolvers in a gqlgen project? In this article, I’ll share practices for testing gqlgen resolvers: from httptest all the way to mocking dependencies.

Why Testing Resolvers Matters

Before diving into the technical details, let’s discuss why testing resolvers is essential.

ReasonBrief Explanation
Validating Business LogicEnsures all business logic runs according to requirements, especially when changes are made to a resolver.
Safe RefactoringGuarantees that code changes (refactoring/optimization) do not alter the intended behavior of the resolver.
Tested Error HandlingEnsures error scenarios, such as a dependency error or data not being found, are handled correctly.
Living DocumentationTest cases serve as living documentation of how a resolver works, especially for engineers new to the project.

GraphQL Architecture Diagram with gqlgen

Let’s do a quick refresher on the architecture:

MERMAID
flowchart TD
    Client -->|GraphQL Query| HTTPHandler
    HTTPHandler -->|GraphQL Request| Resolver
    Resolver -->|Access| Service
    Service -->|Access| DataSource[Database / API / etc]

1. The Client sends a GraphQL Query over HTTP

2. The gqlgen HTTP Handler receives and parses the request

3. The Resolver resolves data by calling the Service

4. The Service fetches data from the DataSource

Case Study: The books Query Resolver

Imagine we have the following schema:

graphql
1type Book {
2  id: ID!
3  title: String!
4  author: String!
5}
6
7type Query {
8  books: [Book!]!
9}

And its resolver fetches data from a service:

go
1type BookService interface {
2    GetBooks(ctx context.Context) ([]*Book, error)
3}

The resolver implementation:

go
1func (r *queryResolver) Books(ctx context.Context) ([]*model.Book, error) {
2    return r.BookService.GetBooks(ctx)
3}

Testing Challenges

  • The resolver depends on a service (BookService)
  • We want to test without accessing the DB (unit test isolation)
  • We want to test end-to-end (integration between components)

Testing gqlgen Resolvers in Golang

We’ll cover the 3 most effective levels of testing:

  1. Unit Testing the Resolver with a Mock (Service Isolation)
  2. Integration Testing the Handler using httptest
  3. End-to-End Testing (optional, out of scope, but I’ll mention it)

1. Unit Testing the Resolver with a Mock

Focuses on the resolver’s business logic and isolating dependencies.

Simulation: Mocking the Service

We’ll create a mock BookService using stretchr/testify/mock .

go
1// book_service_mock.go
2type BookServiceMock struct {
3    mock.Mock
4}
5
6func (m *BookServiceMock) GetBooks(ctx context.Context) ([]*model.Book, error) {
7    args := m.Called(ctx)
8    return args.Get(0).([]*model.Book), args.Error(1)
9}

Unit Testing the Resolver

go
 1func TestBooksResolver(t *testing.T) {
 2    mockSvc := new(BookServiceMock)
 3    dummyBooks := []*model.Book{
 4        {ID: "1", Title: "Clean Code", Author: "Robert Martin"},
 5        {ID: "2", Title: "The Pragmatic Programmer", Author: "Andy Hunt"},
 6    }
 7    mockSvc.On("GetBooks", mock.Anything).Return(dummyBooks, nil)
 8
 9    resolver := &Resolver{BookService: mockSvc}
10
11    ctx := context.Background()
12    result, err := resolver.Query().Books(ctx)
13    
14    assert.NoError(t, err)
15    assert.Equal(t, dummyBooks, result)
16    mockSvc.AssertExpectations(t)
17}

Advantages:

  • Very fast, no infrastructure dependencies.
  • Isolates the resolver logic.

Disadvantages:

  • Doesn’t test serialization (the GraphQL handler).
  • Doesn’t test the interaction behavior between handlers.

2. Integration Testing the GraphQL Handler with httptest

Focuses on the entire pipeline: HTTP handler, parsing, serialization, resolver, and service.

go
 1func TestGraphQLBooksQuery(t *testing.T) {
 2    // Set up the mock service
 3    mockSvc := new(BookServiceMock)
 4    expectedBooks := []*model.Book{
 5        {ID: "1", Title: "Domain-Driven Design", Author: "Eric Evans"},
 6        {ID: "2", Title: "Go in Action", Author: "William Kennedy"},
 7    }
 8    mockSvc.On("GetBooks", mock.Anything).Return(expectedBooks, nil)
 9
10    // Inject the mock into the resolver
11    resolver := &graph.Resolver{BookService: mockSvc}
12
13    // Set up the GraphQL server using the gqlgen handler
14    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
15
16    // Compose the query
17    query := `{"query": "{ books { id title author } }"}`
18    req := httptest.NewRequest(http.MethodPost, "/query", strings.NewReader(query))
19    req.Header.Set("Content-Type", "application/json")
20    w := httptest.NewRecorder()
21
22    srv.ServeHTTP(w, req)
23
24    resp := w.Result()
25    body, _ := io.ReadAll(resp.Body)
26    assert.Equal(t, http.StatusOK, resp.StatusCode)
27
28    // Parse the JSON response
29    var gqlResp struct {
30        Data struct {
31            Books []model.Book `json:"books"`
32        } `json:"data"`
33    }
34    json.Unmarshal(body, &gqlResp)
35
36    assert.Equal(t, len(expectedBooks), len(gqlResp.Data.Books))
37    for i, b := range gqlResp.Data.Books {
38        assert.Equal(t, expectedBooks[i].ID, b.ID)
39        assert.Equal(t, expectedBooks[i].Title, b.Title)
40        assert.Equal(t, expectedBooks[i].Author, b.Author)
41    }
42    mockSvc.AssertExpectations(t)
43}

Flow Overview with mermaid

MERMAID
sequenceDiagram
    participant TestClient as Test (httptest)
    participant HTTPHandler
    participant Resolver
    participant BookServiceMock

    TestClient->>HTTPHandler: POST /query (GraphQL)
    HTTPHandler->>Resolver: resolve Query.books
    Resolver->>BookServiceMock: GetBooks(ctx)
    BookServiceMock-->>Resolver: Dummy Data
    Resolver-->>HTTPHandler: Data
    HTTPHandler-->>TestClient: JSON response

Advantages:

  • Tests the entire GraphQL handler stack.
  • Validates JSON marshalling/unmarshalling.
  • Can test more realistic scenarios (error cases, e.g., GetBooks returning an error).

Disadvantages:

  • Slower than unit tests.
  • The mock needs to be maintained if the interface changes.

3. End-to-End Testing (Optional)

If we want to test integration with other systems (e.g., a real DB, Redis, etc.), end-to-end testing can be done. This usually involves additional tooling such as docker-compose to spin up the entire stack. However, this section is out of scope for this article.


Quick Comparison Table

Unit Test ResolverIntegration Test (httptest)
SpeedVery fastMedium
IsolationYesPartial (Dependency mock)
CoverageResolver logicEnd-to-end handler pipeline
Test DataDummy/mockDummy/mock
RealisticMediumHigh

Best Practices & Tips

  • Mock all external dependencies: Use mocks for services so that tests are deterministic.
  • Use table-driven tests: To test many scenarios in a single file.
  • Test error cases: Make sure there’s a test for when a dependency errors out and returns an error.
  • Don’t mix unit and integration tests: Keep them separate for maintainability.
  • Use a coverage tool: Check coverage for test quality.

Conclusion

Testing in gqlgen is no longer a nightmare. With a combination of httptest and mocks, we can easily test resolvers in isolation or end-to-end without the hassle of setting up a heavy environment. Start with unit testing the resolver, then move up to integration testing with the handler. Make sure dependencies are mocked, use table-driven tests, and always test both the happy path and error cases!

Happy testing! 🚀


References:

I hope this article helps you start your journey writing tests in a gqlgen project. Don’t forget to share it if you found it useful!

Related Articles

💬 Comments