Skip to content
Santekno.com | Level Up Your Engineering Skills
ID
📖 0%
31 Oct 2025 · 5 mnt baca ·Artikel 123 / 125
Go

123 Testing Resolver gqlgen dengan `httptest` dan Mock

IH
Ihsan Arif
Penulis di Santekno · Backend Engineer

123 Testing Resolver gqlgen dengan httptest dan Mock

GraphQL makin populer di ekosistem backend modern. Salah satu alasan utamanya adalah fleksibilitas dan efisiensi query-nya. Di Golang, gqlgen menawarkan tools powerful untuk mengelola GraphQL server, salah satunya meng-generate resolver dari schema. Tapi, tanpa testing yang bagus, kita riskan membuat bug dan behaviour yang tidak diharapkan. Nah, bagaimana sih cara efektif mengetes resolver GraphQL di project gqlgen? Di artikel ini, saya akan berbagi praktik 123 testing resolver gqlgen: dari httptest sampai mock dependency.

Kenapa Testing Resolver itu Penting?

Sebelum diving ke teknis, mari kita bahas mengapa testing resolver itu esensial.

AlasanPenjelasan Singkat
Validasi Logika BisnisMemastikan semua business logic berjalan sesuai requirement, terutama jika terjadi perubahan di resolver.
Refactoring AmanMenjamin perubahan kode (refactor/optimasi) tidak mengubah behaviour yang diinginkan dari resolver.
Error Handling TerujiMemastikan error scenarios, misal dependency error atau data tidak ditemukan, sudah di-handle dengan benar.
Dokumentasi HidupTest case berguna sebagai living documentation cara kerja resolver, terutama untuk engineer baru di proyek.

Skema Arsitektur GraphQL dengan gqlgen

Mari kita refresh sederhana arsitekturnya:

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

1. Client menggunakan HTTP mengirim GraphQL Query

2. gqlgen HTTP Handler menerima dan men-parse request

3. Resolver resolve data dengan memanggil Service

4. Service mengambil data dari DataSource

Studi Kasus: Resolver Query books

Bayangkan kita punya schema berikut:

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

Dan resolvernya mengambil data dari service:

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

Implementasi resolvernya:

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

Tantangan Testing

  • Resolver tergantung service (BookService)
  • Ingin testing tanpa akses DB (isolasi unit test)
  • Ingin testing end-to-end (integrasi antar komponen)

123 Testing gqlgen Resolver di Golang

Kita akan bahas 3 level test paling efektif:

  1. Unit Test Resolver dengan Mock (Isolasi Service)
  2. Integration Test Handler pakai httptest
  3. End-to-End Test (optional, keluar scope, tapi akan saya mention)

1. Unit Test Resolver dengan Mock

Fokus ke business logic resolver dan isolasi dependency.

Simulasi: Mock Service

Kita bikin mock BookService pakai 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 Test 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}

Kelebihan:

  • Sangat cepat, tidak bergantung infra.
  • Isolasi logic resolver.

Kekurangan:

  • Tidak mengetes serialization (GraphQL handler).
  • Tidak mengetes behaviour interaksi antar handler.

2. Integration Test GraphQL Handler dengan httptest

Fokus ke seluruh pipeline: HTTP handler, parsing, serialization, resolver, dan service.

go
 1func TestGraphQLBooksQuery(t *testing.T) {
 2    // Setup 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 mock ke resolver
11    resolver := &graph.Resolver{BookService: mockSvc}
12
13    // Setup server GraphQL pakai gqlgen handler
14    srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
15
16    // Compose 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 response JSON
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}

Gambaran Flow dengan 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

Kelebihan:

  • Menguji seluruh stack GraphQL handler.
  • Validasi marshalling/unmarshalling JSON.
  • Bisa test scenarios lebih realistis (error case, misal GetBooks return error).

Kekurangan:

  • Lebih lambat dari unit test.
  • Mock perlu di-maintain jika interface berubah.

3. End-to-End Test (Opsional)

Jika kita mau test integrasi dengan sistem lain (misal DB asli, Redis, dsb), end-to-end test bisa dilakukan. Biasanya pakai tool tambahan seperti docker-compose untuk spin up seluruh stack. Tapi, section ini di luar scope artikel kali ini.


Tabel Komparasi Cepat

Unit Test ResolverIntegration Test (httptest)
SpeedSangat cepatSedang
IsolasiYaPartial (Dependency mock)
CoverageLogic resolverEnd-to-end pipeline handler
Test DataDummy/mockDummy/mock
RealisticMediumHigh

Best Practice & Tips

  • Mock all external dependencies: Gunakan mock untuk service agar test deterministic.
  • Gunakan table-driven tests: Untuk test banyak scenario di 1 file.
  • Test error case: Pastikan ada test jika dependency error, return error.
  • Jangan gabungkan unit dan integration test: Pisahkan untuk maintainability.
  • Gunakan coverage tool: Cek coverage untuk kualitas tests.

Kesimpulan

Testing di gqlgen bukan lagi mimpi buruk. Dengan kombinasi httptest dan mock, kita bisa dengan mudah nge-test resolver secara isolated maupun end-to-end tanpa pusing setup environment berat. Mulailah dari unit test resolver, naik ke integration test pakai handler. Pastikan dependency di-mock, gunakan table-driven, dan selalu test happy maupun error case!

Happy testing! 🚀


Referensi:

Semoga artikel ini membantu memulai journey kamu menulis test di project gqlgen. Jangan lupa share jika bermanfaat!

Artikel Terkait

💬 Komentar