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

42 Writing Unit Tests for Queries in graphql-go

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

42 Writing Unit Tests for Queries in graphql-go

As modern backend application developers, GraphQL is nothing new to us. Query flexibility, data efficiency, and self-documentation are the reasons many teams have switched to GraphQL. In the Go ecosystem, the graphql-go library has become a popular choice. However, one thing is often overlooked when adopting GraphQL in Go: Unit Testing.
Unit tests are not just a formality. They serve as an insurance policy against future bugs, especially for queries that act as the main bridge between client and server. This article breaks down, step by step, how to write unit tests for queries in graphql-go, complete with code examples, mock data simulations, and best practices.


Why Unit Test Queries?

Table: Benefits of Unit Testing GraphQL Queries

BenefitExplanation
Ensures CorrectnessMake sure the response structure and data resolution are correct
Supports RefactoringAdding/modifying resolvers is safe because tests are ready to catch regressions
Living DocumentationBackend consumers learn the use cases and responses by reading the test cases
Tests Error HandlingSimulating error scenarios (e.g., unauthorized, empty data) becomes easy

GraphQL-Go: Installation and Basic Schema

Before diving into unit tests, let’s set up a simple GraphQL schema in Go. To stay focused, we’ll create the query getUser(id: ID!): User.

go
 1type User struct {
 2    ID   string
 3    Name string
 4}
 5
 6type Resolver struct{}
 7
 8func (r *Resolver) GetUser(ctx context.Context, args struct{ ID string }) (*User, error) {
 9    // Scenario: Fetch a user by ID, or return an error if not found
10    if args.ID == "42" {
11        return &User{ID: "42", Name: "Arthur Dent"}, nil
12    }
13    return nil, errors.New("user not found")
14}
15
16// schema.graphql
17/*
18type User {
19    id: ID!
20    name: String!
21}
22
23type Query {
24    getUser(id: ID!): User
25}
26*/

The integration into the server usually looks like this:

go
1schema := graphql.MustParseSchema(schemaString, &Resolver{})
2http.Handle("/query", &relay.Handler{Schema: schema})

Fundamentals of Unit Testing Queries in graphql-go

Unit tests in graphql-go do not hit the HTTP endpoint. They invoke the resolver directly through the schema, passing the query and variables. The advantage is that unit tests can run quickly and remain isolated from middleware/auth/network.

Dependencies

Add these to go.mod:

go
1require (
2    github.com/graph-gophers/graphql-go v1.7.0
3    github.com/stretchr/testify v1.8.0
4)

Writing a Simple Unit Test

Let’s create the test file: resolver_test.go.
Remember, a Go unit test must use a function with the signature TestXxx(t *testing.T) so that go test recognizes it.

go
 1package main
 2
 3import (
 4    "context"
 5    "testing"
 6
 7    "github.com/graph-gophers/graphql-go"
 8    "github.com/stretchr/testify/assert"
 9)
10
11// Simplify the schema for testing
12const testSchema = `
13type User {
14    id: ID!
15    name: String!
16}
17
18type Query {
19    getUser(id: ID!): User
20}
21`
22
23// Mock resolver implementation for testing
24type testResolver struct{}
25
26func (r *testResolver) GetUser(ctx context.Context, args struct{ ID string }) (*User, error) {
27    if args.ID == "42" {
28        return &User{ID: "42", Name: "Tester"}, nil
29    }
30    return nil, nil
31}
32
33func TestGetUserQuery(t *testing.T) {
34    schema := graphql.MustParseSchema(testSchema, &testResolver{})
35
36    query := `
37        query ($id: ID!) {
38            getUser(id: $id) {
39                id
40                name
41            }
42        }
43    `
44    vars := map[string]interface{}{
45        "id": "42",
46    }
47
48    // Execute the query directly against the schema (not over HTTP)
49    response := schema.Exec(context.Background(), query, "GetUser", vars)
50
51    // The status code is always 200, so check the data and errors instead
52    expected := `{"getUser":{"id":"42","name":"Tester"}}`
53    assert.JSONEq(t, expected, string(response.Data))
54    assert.Nil(t, response.Errors)
55}

Simulating Unit Tests with Different Data

The tests should cover:

  • A successful data query
  • A query for data that is not found (should return nil)
  • A query with an invalid argument

Simulation Schema

Here is a simulation of the expected branching cases:

MERMAID
flowchart TD
    A[Start Test] --> B{Input ID?}
    B -- "42" --> C[Data Ada]
    C --> F[Sukses response]
    B -- "99" --> D[Data Tidak Ada]
    D --> G[Response Null]
    B -- "" --> E[Invalid Argumen]
    E --> H[Error Handling]

Start Writing the Table Test

Define a test table to keep things maintainable.

go
 1func TestGetUserQuery_TableDriven(t *testing.T) {
 2    schema := graphql.MustParseSchema(testSchema, &testResolver{})
 3    tests := []struct {
 4        name     string
 5        inputID  string
 6        expData  string
 7        hasError bool
 8    }{
 9        {"ID exists", "42", `{"getUser":{"id":"42","name":"Tester"}}`, false},
10        {"ID not found", "99", `{"getUser":null}`, false},
11    }
12
13    for _, tc := range tests {
14        t.Run(tc.name, func(t *testing.T) {
15            query := `
16                query ($id: ID!) {
17                    getUser(id: $id) {
18                        id
19                        name
20                    }
21                }
22            `
23            vars := map[string]interface{}{
24                "id": tc.inputID,
25            }
26            resp := schema.Exec(context.Background(), query, "GetUser", vars)
27            assert.JSONEq(t, tc.expData, string(resp.Data))
28        })
29    }
30}

Testing Errors & Edge Cases

What happens if an argument isn’t sent?
Executing a query with a missing argument will return an error. Let’s simulate this in a test as well.

go
 1func TestGetUserQuery_MissingArg(t *testing.T) {
 2    schema := graphql.MustParseSchema(testSchema, &testResolver{})
 3    query := `
 4        query {
 5            getUser { id name }
 6        }
 7    `
 8    resp := schema.Exec(context.Background(), query, "", nil)
 9    assert.NotNil(t, resp.Errors)
10    assert.Contains(t, resp.Errors[0].Message, "missing required argument")
11}

Mocking Dependencies

A production resolver usually fetches data via a database/repository/another service. In unit tests, avoid depending on external resources!
Use the dependency injection pattern: the resolver accepts a service interface, and in the test, you inject a mock implementation.

go
 1type UserService interface {
 2    FindByID(ctx context.Context, id string) (*User, error)
 3}
 4
 5type ResolverWithDep struct {
 6    Svc UserService
 7}
 8
 9func (r *ResolverWithDep) GetUser(ctx context.Context, args struct{ ID string }) (*User, error) {
10    return r.Svc.FindByID(ctx, args.ID)
11}
12
13// Mocking approach: you can use gomock/mockery, or just a simple dummy struct
14type mockUserSvc struct{}
15
16func (m *mockUserSvc) FindByID(ctx context.Context, id string) (*User, error) {
17    if id == "42" {
18        return &User{ID: "42", Name: "Arthur"}, nil
19    }
20    return nil, nil
21}
22
23// In the test:
24schema := graphql.MustParseSchema(testSchema, &ResolverWithDep{Svc: &mockUserSvc{}})

Unit testing with mocks ensures the resolver is tested only on its logic, without depending on external services. This practice is important in a real world microservice.


Wrap-up: GraphQL Query Unit Test Checklist

Here is a checklist to make our testing solid in real projects:

  • Successful query
  • Query with data not found/null
  • Invalid/missing argument
  • Error simulation (e.g., dependency failure)
  • Table test for maintainability
  • Dependency isolation via mocks
  • Check both data and errors in the response

Conclusion

Writing unit tests for queries in graphql-go is not difficult, but it is extremely crucial. Testing directly against the schema without HTTP saves time, makes refactoring easier, and increases confidence. With table driven tests, flow diagrams, and mocked dependencies, we’re ready to build robust and scalable GraphQL backends.

Never ship a GraphQL query without unit test coverage.
With solid coverage, we can sleep soundly — without fear of bugs quietly breaking production!


References:

See you in the next workshop. 🚀

Related Articles

💬 Comments