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.
| Alasan | Penjelasan Singkat |
|---|---|
| Validasi Logika Bisnis | Memastikan semua business logic berjalan sesuai requirement, terutama jika terjadi perubahan di resolver. |
| Refactoring Aman | Menjamin perubahan kode (refactor/optimasi) tidak mengubah behaviour yang diinginkan dari resolver. |
| Error Handling Teruji | Memastikan error scenarios, misal dependency error atau data tidak ditemukan, sudah di-handle dengan benar. |
| Dokumentasi Hidup | Test case berguna sebagai living documentation cara kerja resolver, terutama untuk engineer baru di proyek. |
Skema Arsitektur GraphQL dengan gqlgen
Mari kita refresh sederhana arsitekturnya:
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:
type Book {
id: ID!
title: String!
author: String!
}
type Query {
books: [Book!]!
}
Dan resolvernya mengambil data dari service:
type BookService interface {
GetBooks(ctx context.Context) ([]*Book, error)
}
Implementasi resolvernya:
func (r *queryResolver) Books(ctx context.Context) ([]*model.Book, error) {
return r.BookService.GetBooks(ctx)
}
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:
- Unit Test Resolver dengan Mock (Isolasi Service)
- Integration Test Handler pakai
httptest - 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.
// book_service_mock.go
type BookServiceMock struct {
mock.Mock
}
func (m *BookServiceMock) GetBooks(ctx context.Context) ([]*model.Book, error) {
args := m.Called(ctx)
return args.Get(0).([]*model.Book), args.Error(1)
}
Unit Test Resolver
func TestBooksResolver(t *testing.T) {
mockSvc := new(BookServiceMock)
dummyBooks := []*model.Book{
{ID: "1", Title: "Clean Code", Author: "Robert Martin"},
{ID: "2", Title: "The Pragmatic Programmer", Author: "Andy Hunt"},
}
mockSvc.On("GetBooks", mock.Anything).Return(dummyBooks, nil)
resolver := &Resolver{BookService: mockSvc}
ctx := context.Background()
result, err := resolver.Query().Books(ctx)
assert.NoError(t, err)
assert.Equal(t, dummyBooks, result)
mockSvc.AssertExpectations(t)
}
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.
func TestGraphQLBooksQuery(t *testing.T) {
// Setup mock service
mockSvc := new(BookServiceMock)
expectedBooks := []*model.Book{
{ID: "1", Title: "Domain-Driven Design", Author: "Eric Evans"},
{ID: "2", Title: "Go in Action", Author: "William Kennedy"},
}
mockSvc.On("GetBooks", mock.Anything).Return(expectedBooks, nil)
// Inject mock ke resolver
resolver := &graph.Resolver{BookService: mockSvc}
// Setup server GraphQL pakai gqlgen handler
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: resolver}))
// Compose query
query := `{"query": "{ books { id title author } }"}`
req := httptest.NewRequest(http.MethodPost, "/query", strings.NewReader(query))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
resp := w.Result()
body, _ := io.ReadAll(resp.Body)
assert.Equal(t, http.StatusOK, resp.StatusCode)
// Parse response JSON
var gqlResp struct {
Data struct {
Books []model.Book `json:"books"`
} `json:"data"`
}
json.Unmarshal(body, &gqlResp)
assert.Equal(t, len(expectedBooks), len(gqlResp.Data.Books))
for i, b := range gqlResp.Data.Books {
assert.Equal(t, expectedBooks[i].ID, b.ID)
assert.Equal(t, expectedBooks[i].Title, b.Title)
assert.Equal(t, expectedBooks[i].Author, b.Author)
}
mockSvc.AssertExpectations(t)
}
Gambaran Flow dengan 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 Resolver | Integration Test (httptest) | |
|---|---|---|
| Speed | Sangat cepat | Sedang |
| Isolasi | Ya | Partial (Dependency mock) |
| Coverage | Logic resolver | End-to-end pipeline handler |
| Test Data | Dummy/mock | Dummy/mock |
| Realistic | Medium | High |
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!