title: “84. Mock gRPC Server dengan gomock”
author: “Senior Software Engineer”
date: 2024-06-26
Mock gRPC Server dengan gomock
Dalam pengembangan perangkat lunak modern, pengujian menjadi salah satu pilar utama menjaga kualitas software, khususnya dalam sistem yang memanfaatkan arsitektur microservices dan komunikasi RPC (Remote Procedure Call) seperti gRPC. Salah satu tantangan dalam menguji service layer berbasis gRPC adalah bagaimana melakukan mock pada server lawan bicara. Untungnya, ekosistem Go sudah menyediakan tool powerful bernama gomock. Pada artikel ini, saya akan membahas secara lengkap bagaimana memanfaatkan gomock untuk membuat mock server gRPC, disertai contoh kode, simulasi, serta diagram alur untuk membantu visualisasi.
Mengapa Perlu Mock gRPC Server?
Sebelum masuk ke teknis, mari kita pahami dulu skenario berikut:
Bayangkan kita membangun sebuah service A yang berkomunikasi dengan service B menggunakan gRPC. Service B adalah third-party service atau sedang dikembangkan tim lain. Jika kita ingin melakukan testing pada service A secara isolated, memanggil service B secara langsung akan membuat test flakey (tergantung service B), memperlambat eksekusi, dan sulit dikendalikan.
Solusinya? Jika kita bisa melakukan mock pada gRPC server, maka kita bisa:
- Mendapat respons sesuai skenario yang kita inginkan (happy/edge case).
- Mengukur luas coverage logic di Service A tanpa perlu bergantung pada Service B.
- Menjalankan test dengan lebih cepat dan reliable.
Gambaran Alur Pengujian dengan Mock
Menggunakan mock pada gRPC dapat dideskripsikan dengan flow sederhana seperti berikut:
flowchart LR
TestCase[Pengujian (unit/integration)]
MockServer[Mock gRPC Server (gomock)]
ServiceA[Service A (Client)]
ServiceA --Request--> MockServer
MockServer --Response--> ServiceA
TestCase --Assertion/Validation--> ServiceA
Pada diagram di atas, TestCase membangun mock server lalu menyuntikannya ke Service A. Setiap kali Service A membuat request, mock server akan merespons sesuai dengan yang telah diatur pada skenario pengujian.
Struktur Proyek
Agar lebih real, saya akan ambil studi kasus sederhana:
- Definisikan protoc file gRPC (misal service
UserService) - Generate kode Go dari Protobuf
- Buat interface mock dengan
gomock - Implementasi unit test dengan mock server
Contoh struktur direktori sederhana:
.
├── proto/
│ └── user.proto
├── pb/
│ └── user.pb.go
├── internal/
│ ├── service.go
│ └── service_test.go
└── go.mod
1. Definisikan File Protobuf
Misal, file proto/user.proto:
syntax = "proto3";
package pb;
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
string id = 1;
string name = 2;
}
2. Generate gRPC Stub
Generate kode Go:
protoc --go_out=pb --go-grpc_out=pb proto/user.proto
Hasilnya, di folder pb/ akan tersedia user.pb.go dan user_grpc.pb.go yang berisi interface client & server (untuk UserService):
// Interface server yang akan di-mock
type UserServiceServer interface {
GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
}
3. Generate Mock Interface dengan gomock
Install mockgen dari gomock jika belum ada:
go install github.com/golang/mock/mockgen@latest
Generate mock untuk interface server:
mockgen -source=pb/user_grpc.pb.go -destination=internal/mock_user.go -package=internal
File internal/mock_user.go akan berisi struct MockUserServiceServer bila ingin mocking server.
4. Implementasi Unit Test dengan Mock
Misal kita punya logic di internal/service.go seperti berikut:
package internal
import (
"context"
"pb"
)
type Handler struct {
userService pb.UserServiceClient
}
func NewHandler(client pb.UserServiceClient) *Handler {
return &Handler{userService: client}
}
func (h *Handler) FindUser(ctx context.Context, id string) (string, error) {
resp, err := h.userService.GetUser(ctx, &pb.GetUserRequest{Id: id})
if err != nil {
return "", err
}
return resp.Name, nil
}
Simulasi Pengujian: Mock gRPC Server Client
Skenario unit testnya:
- Kita ingin memastikan bahwa ketika service
FindUsermemanggilGetUser, kita bisa mengontrol respons (misal name=“John Doe”), dan menguji logika kita tanpa benar-benar memanggil server asli.
Berikut contoh kode di internal/service_test.go:
package internal
import (
"context"
"testing"
"github.com/golang/mock/gomock"
"pb"
)
func TestFindUser(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockUserSrv := NewMockUserServiceClient(ctrl)
// Setup mock: setiap GetUser dipanggil dengan id '123', balas John Doe
mockUserSrv.EXPECT().
GetUser(gomock.Any(), &pb.GetUserRequest{Id: "123"}).
Return(&pb.GetUserResponse{Id: "123", Name: "John Doe"}, nil)
handler := NewHandler(mockUserSrv)
name, err := handler.FindUser(context.Background(), "123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if name != "John Doe" {
t.Fatalf("expected John Doe, got %v", name)
}
}
Tabel: Perbandingan Testing Tanpa Mock vs Dengan Mock
| Metode | Kelebihan | Kekurangan |
|---|---|---|
| Tanpa mock | Real end-to-end, mengetahui bug antar service | Lambat, fragile, setup sulit |
Dengan gomock | Cepat, isolated, deterministik | Tak meng-cover bug real server |
Best Practice dalam Mocking gRPC dengan gomock
Hanya mock yang perlu
Mock hanya behavior yang menjadi dependency, bukan SUT (System Under Test)-nya.Gunakan .EXPECT() se-ekspresif mungkin
Agar tahu persis parameter berapa kali dipanggil, gunakan .AnyTimes(), .Times(), atau matchers lain darigomock.Pisahkan logic mock di helper function
Jika test cukup kompleks, reuse pengaturan mock di helper validasi untuk code cleanliness.
Kapan Lebih Baik Mock Server Daripada Integration Test?
Gunakan mock saat:
- Menulis unit test untuk komponen logic yang berinteraksi dengan dependency via gRPC.
- Ingin test cepat dan repeatable.
Gunakan integration test asli saat:
- Ingin memastikan benar-benar kompatibel antar service.
- Mendekati staging/production.
Kesimpulan
Mock gRPC server menggunakan gomock membuat workflow pengujian Go menjadi jauh lebih robust, cepat, dan maintainable. Dengan men-generate mock dari Protobuf interface, developer dapat dengan mudah menguji kemampuan client/service layer tanpa khawatir akan kerumitan dan ketidakstabilan di server eksternal. Ini adalah skill wajib untuk Go engineer yang menangani distributed system.
Referensi
Selamat mencoba! Jangan ragu eksplorasi pengujian distributed system-mu dengan lebih fleksibel menggunakan mock!