Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
28 Aug 2025 · 5 min read ·Article 81 / 110
Go

81. Unit Testing for gRPC Handlers

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

title: “81. Unit Testing for gRPC Handlers: A Complete Guide for Engineers” description: Learn the strategies, code examples, and best practices for writing high-quality unit tests for gRPC handlers in modern backend services. date: 2024-06-12 author: Seno Adi Prasetyo tags:

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

gRPC has become the backbone of many modern backend applications. It’s no surprise: its high performance and type-safe system make gRPC a popular choice for building microservices and server-to-server communication. But the stability of a service is not determined by performance alone—it also depends on the quality of how we test our code. One of the most vital forms of testing is the unit test, especially for gRPC handlers.

In this article, I’ll explain how to implement effective unit tests for gRPC handlers, complete with a case study using Go. I’ll also provide a comparison table of testing strategies, a simulation, and a flow diagram using mermaid that you can adapt to other languages.


Why Do gRPC Handlers Need Unit Tests?

A gRPC handler is the entry point for all client request logic. That’s where validation, business rules, and interactions with external dependencies all come together. Without unit tests, even the smallest bug can cause downtime and turn into a nightmare for the entire team.

Here are some of the main reasons why unit tests for gRPC handlers matter:

ReasonExplanation
Input ValidationEnsures incoming requests are properly validated
Business Logic SimulationTests business logic before it’s actually wired up to a database/other service
Regression ProofPrevents old bugs from reappearing after a code update
Implicit DocumentationGood test cases = another way to read a handler’s business flow
Confidence to RefactorReduces the risk of changing code

Anatomy of a Simple gRPC Handler

Let’s look at a simple example of a gRPC handler written in Go.

go
 1// protos/user.proto
 2service UserService {
 3    rpc GetUser(GetUserRequest) returns (GetUserResponse) {}
 4}
 5
 6message GetUserRequest {
 7    string id = 1;
 8}
 9
10message GetUserResponse {
11    string id = 1;
12    string name = 2;
13    string email = 3;
14}

The handler looks roughly like this:

go
 1// internal/handler/user.go
 2type UserRepository interface {
 3    FindByID(ctx context.Context, id string) (*User, error)
 4}
 5
 6type UserServiceHandler struct {
 7    repo UserRepository
 8}
 9
10func (h *UserServiceHandler) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
11    user, err := h.repo.FindByID(ctx, req.Id)
12    if err != nil {
13        return nil, status.Errorf(codes.NotFound, "user not found")
14    }
15    return &pb.GetUserResponse{
16        Id:    user.ID,
17        Name:  user.Name,
18        Email: user.Email,
19    }, nil
20}

Note: The example above already separates the dependency (UserRepository) to make it easier to test.


Strategies for Unit Testing gRPC Handlers

When writing unit tests for gRPC handlers, dependencies should be mocked. Don’t actually query the database or call an external service.

Handler Testing Flow Diagram

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

Testing Packages You Can Use

LanguageTest PackageMock Package
Gotestinggithub.com/stretchr/testify/mock, gomock
JavaJUnitMockito
Pythonunittestunittest.mock
Nodejestsinon

For Go, we’ll use testify to keep the code clean and easy to read.


Example of a gRPC Handler Unit Test (Go)

Creating a Mock Repository

go
 1// internal/repository/user_mock.go
 2import "github.com/stretchr/testify/mock"
 3
 4type MockUserRepository struct {
 5    mock.Mock
 6}
 7
 8func (m *MockUserRepository) FindByID(ctx context.Context, id string) (*User, error) {
 9    args := m.Called(ctx, id)
10    if user, ok := args.Get(0).(*User); ok {
11        return user, args.Error(1)
12    }
13    return nil, args.Error(1)
14}

Writing the Unit Test for the Handler

go
 1// internal/handler/user_test.go
 2import (
 3    "context"
 4    "testing"
 5    "github.com/stretchr/testify/assert"
 6    "yourproject/internal/repository"
 7    pb "yourproject/protos"
 8)
 9
10func TestGetUser_Success(t *testing.T) {
11    // Setup
12    mockRepo := new(repository.MockUserRepository)
13    handler := &UserServiceHandler{repo: mockRepo}
14    testUser := &User{ID: "1", Name: "Budi", Email: "budi@email.com"}
15
16    // Arrange
17    mockRepo.On("FindByID", mock.Anything, "1").Return(testUser, nil)
18
19    // Act
20    req := &pb.GetUserRequest{Id: "1"}
21    resp, err := handler.GetUser(context.Background(), req)
22
23    // Assert
24    assert.NoError(t, err)
25    assert.NotNil(t, resp)
26    assert.Equal(t, "1", resp.Id)
27    assert.Equal(t, "Budi", resp.Name)
28    assert.Equal(t, "budi@email.com", resp.Email)
29    mockRepo.AssertExpectations(t)
30}
31
32func TestGetUser_NotFound(t *testing.T) {
33    mockRepo := new(repository.MockUserRepository)
34    handler := &UserServiceHandler{repo: mockRepo}
35
36    mockRepo.On("FindByID", mock.Anything, "2").Return(nil, errors.New("not found"))
37
38    req := &pb.GetUserRequest{Id: "2"}
39    resp, err := handler.GetUser(context.Background(), req)
40
41    assert.Error(t, err)
42    assert.Nil(t, resp)
43    assert.Contains(t, err.Error(), "user not found")
44    mockRepo.AssertExpectations(t)
45}

Simulating Test Execution

With the code above, the tests will run like this:

shell
1=== RUN   TestGetUser_Success
2--- PASS: TestGetUser_Success (0.00s)
3=== RUN   TestGetUser_NotFound
4--- PASS: TestGetUser_NotFound (0.00s)
5PASS
6ok  	yourproject/internal/handler 	0.005s

Best Practices for Unit Testing gRPC Handlers

Here are some important best practices to apply:

  1. Isolate Dependencies
    • Use the interface & mock pattern so the handler can be tested in isolation from the DB/external services.
  2. Test Every Scenario
    • Cover success, not found, invalid input, and other error branches.
  3. Minimize Side Effects
    • The handler should ideally be pure: change external state as little as possible within the scope of the test.
  4. Be Explicit About Expectations
    • Check the returned error, the message, and the mock calls in detail.
  5. Automate in CI
    • Make sure all unit tests run automatically in your CI/CD pipeline.

When Should You Use Integration Tests or E2E?

Unit tests only verify logic in isolation. If you want to test the entire flow (handler, network, database), use integration tests or E2E tests.
Still, unit tests for handlers remain a must have because they execute quickly and the cost of finding a bug with them is cheap.


Conclusion

Unit testing gRPC handlers isn’t just about increasing coverage—it’s about the reliability of your service. With a strategy of dependency isolation, mocking, and broad scenario coverage, your engineering team will have the peace of mind to develop new features without fear that old bugs will suddenly resurface.

I hope that with the examples, simulation, and diagrams above, you’ll feel even more confident writing unit tests for gRPC handlers in your next project 🚀


References


Happy testing!

Related Articles

💬 Comments