tutorial

  1. Unit Test untuk Handler gRPC


title: “81. Unit Test untuk Handler gRPC: Panduan Lengkap bagi Engineer” description: Pelajari strategi, contoh kode, serta best practice membuat unit test berkualitas untuk handler gRPC di layanan backend modern. date: 2024-06-12 author: Seno Adi Prasetyo tags:

  • grpc
  • unit-testing
  • backend
  • software-engineering
  • go

gRPC kini menjadi tulang punggung banyak aplikasi backend modern. Tak heran, performanya yang tinggi dan sistem type-safe membuat gRPC digemari untuk membangun microservice dan layanan antar server. Tetapi, stabilitas layanan tak hanya ditentukan oleh performa—melainkan juga oleh kualitas pengujian kode kita. Salah satu pengujian vital adalah unit test, terutama pada handler gRPC.

Pada artikel ini, saya akan membahas bagaimana menerapkan unit test yang efektif untuk handler gRPC, dilengkapi dengan studi kasus menggunakan bahasa Go. Saya juga akan memberikan tabel perbandingan strategi testing, simulasi, hingga diagram alur menggunakan mermaid yang bisa Anda adaptasi ke bahasa lain.


Mengapa Perlu Unit Test untuk Handler gRPC?

Handler gRPC adalah entry point seluruh logic permintaan klien. Di situlah validasi, business rule, hingga interaksi dengan dependensi eksternal digabungkan. Tanpa unit test, setiap bug kecil berpotensi menimbulkan downtime dan mimpi buruk bagi seluruh tim.

Berikut beberapa alasan utama mengapa unit test untuk handler gRPC penting:

AlasanPenjelasan
Validasi InputMemastikan request yang masuk telah tervalidasi dengan benar
Simulasi logika bisnisMenguji business logic sebelum benar-benar terhubung ke database/service lain
Regression ProofMenghindari bug lama yang muncul lagi setelah update kode
Dokumentasi implicitTest case bagus = cara lain membaca alur bisnis handler
Confidence untuk refactorRisiko perubahan kode diperkecil

Anatomy Handler gRPC Sederhana

Mari kita lihat contoh sederhana handler gRPC menggunakan Go.

// protos/user.proto
service UserService {
    rpc GetUser(GetUserRequest) returns (GetUserResponse) {}
}

message GetUserRequest {
    string id = 1;
}

message GetUserResponse {
    string id = 1;
    string name = 2;
    string email = 3;
}

Handler-nya kira-kira seperti ini:

// internal/handler/user.go
type UserRepository interface {
    FindByID(ctx context.Context, id string) (*User, error)
}

type UserServiceHandler struct {
    repo UserRepository
}

func (h *UserServiceHandler) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
    user, err := h.repo.FindByID(ctx, req.Id)
    if err != nil {
        return nil, status.Errorf(codes.NotFound, "user not found")
    }
    return &pb.GetUserResponse{
        Id:    user.ID,
        Name:  user.Name,
        Email: user.Email,
    }, nil
}

Catatan: Contoh di atas sudah dipisahkan dependensi (UserRepository) agar easier to test.


Strategi Unit Testing Handler gRPC

Ketika menulis unit test handler gRPC, dependencies sebaiknya di-mock. Jangan benar-benar query ke database atau eksternal service.

Diagram Alur Testing Handler

flowchart TD
    A[Setup Test] --> B[Mock Dependensi]
    B --> C[Panggil Handler dengan Request]
    C --> D[Verifikasi Response & Error]
    D --> E[Bersihkan dan Selesai]

Paket Testing yang Bisa Dipakai

BahasaPaket TestPaket Mock
Gotestinggithub.com/stretchr/testify/mock, gomock
JavaJUnitMockito
Pythonunittestunittest.mock
Nodejestsinon

Untuk Go, kita akan gunakan testify agar kode tetap bersih dan mudah dibaca.


Contoh Unit Test Handler gRPC (Go)

Membuat Mock Repository

// internal/repository/user_mock.go
import "github.com/stretchr/testify/mock"

type MockUserRepository struct {
    mock.Mock
}

func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
    args := m.Called(ctx, id)
    if user, ok := args.Get(0).(*User); ok {
        return user, args.Error(1)
    }
    return nil, args.Error(1)
}
// internal/handler/user_test.go
import (
    "context"
    "testing"
    "github.com/stretchr/testify/assert"
    "yourproject/internal/repository"
    pb "yourproject/protos"
)

func TestGetUser_Success(t *testing.T) {
    // Setup
    mockRepo := new(repository.MockUserRepository)
    handler := &UserServiceHandler{repo: mockRepo}
    testUser := &User{ID: "1", Name: "Budi", Email: "budi@email.com"}

    // Arrange
    mockRepo.On("FindByID", mock.Anything, "1").Return(testUser, nil)

    // Act
    req := &pb.GetUserRequest{Id: "1"}
    resp, err := handler.GetUser(context.Background(), req)

    // Assert
    assert.NoError(t, err)
    assert.NotNil(t, resp)
    assert.Equal(t, "1", resp.Id)
    assert.Equal(t, "Budi", resp.Name)
    assert.Equal(t, "budi@email.com", resp.Email)
    mockRepo.AssertExpectations(t)
}

func TestGetUser_NotFound(t *testing.T) {
    mockRepo := new(repository.MockUserRepository)
    handler := &UserServiceHandler{repo: mockRepo}

    mockRepo.On("FindByID", mock.Anything, "2").Return(nil, errors.New("not found"))

    req := &pb.GetUserRequest{Id: "2"}
    resp, err := handler.GetUser(context.Background(), req)

    assert.Error(t, err)
    assert.Nil(t, resp)
    assert.Contains(t, err.Error(), "user not found")
    mockRepo.AssertExpectations(t)
}

Simulasi Eksekusi Test

Dengan kode di atas, test akan berjalan seperti berikut:

=== RUN   TestGetUser_Success
--- PASS: TestGetUser_Success (0.00s)
=== RUN   TestGetUser_NotFound
--- PASS: TestGetUser_NotFound (0.00s)
PASS
ok  	yourproject/internal/handler 	0.005s

Best Practice Unit Testing Handler gRPC

Berikut beberapa best practice yang penting diterapkan:

  1. Isolasi Dependensi
    • Gunakan interface & mock pattern agar handler mudah diuji terpisah dari DB/service eksternal.
  2. Uji Semua Skenario
    • Cover success, not found, invalid input, dan error branch lain.
  3. Minimalkan Side Effect
    • Handler sebaiknya pure: ubah state eksternal sekecil mungkin dalam scope test.
  4. Eksplisit Expectation
    • Periksa return error, message, dan panggilan mock dengan detail.
  5. Automasi di CI
    • Pastikan seluruh unit test dijalankan otomatis dalam pipeline CI/CD.

Kapan Gunakan Integration Test atau E2E?

Unit test hanya menguji isolasi logic. Jika ingin menguji flow utuh (handler, network, database), gunakan integration test/E2E.
Tetapi, unit test handler tetap must have karena cepat dieksekusi dan harga temuan bug-nya murah.


Kesimpulan

Unit test pada handler gRPC bukan hanya soal menaikkan coverage, tapi juga soal keandalan layanan. Dengan strategi dependency isolation, mock, dan cakupan skenario luas, engineering team akan memiliki peace of mind mengembangkan fitur baru tanpa takut bug lawas bangkit tiba-tiba.

Semoga dengan contoh, simulasi, dan diagram di atas, Anda makin mantap menulis unit test gRPC handler di proyek Anda berikutnya 🚀


Referensi


Happy testing!

comments powered by Disqus