tutorial

  1. Mock gRPC Server dengan gomock


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:

  1. Definisikan protoc file gRPC (misal service UserService)
  2. Generate kode Go dari Protobuf
  3. Buat interface mock dengan gomock
  4. 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 FindUser memanggil GetUser, 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

MetodeKelebihanKekurangan
Tanpa mockReal end-to-end, mengetahui bug antar serviceLambat, fragile, setup sulit
Dengan gomockCepat, isolated, deterministikTak meng-cover bug real server

Best Practice dalam Mocking gRPC dengan gomock

  1. Hanya mock yang perlu
    Mock hanya behavior yang menjadi dependency, bukan SUT (System Under Test)-nya.

  2. Gunakan .EXPECT() se-ekspresif mungkin
    Agar tahu persis parameter berapa kali dipanggil, gunakan .AnyTimes(), .Times(), atau matchers lain dari gomock.

  3. 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!

comments powered by Disqus