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

84. Mocking a gRPC Server with `gomock`

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

title: “84. Mocking a gRPC Server with gomock” author: “Senior Software Engineer” date: 2024-06-26

Mocking a gRPC Server with gomock

In modern software development, testing has become one of the main pillars for maintaining software quality, especially in systems that rely on a microservices architecture and RPC (Remote Procedure Call) communication such as gRPC. One of the challenges in testing a gRPC-based service layer is figuring out how to mock the server on the other end of the conversation. Fortunately, the Go ecosystem already provides a powerful tool called gomock . In this article, I’ll walk through in detail how to use gomock to build a mock gRPC server, complete with code examples, simulations, and flow diagrams to help you visualize everything.


Why Do We Need to Mock a gRPC Server?

Before diving into the technical details, let’s first understand the following scenario:

Imagine we are building a Service A that communicates with a Service B using gRPC. Service B is a third-party service or is still being developed by another team. If we want to test Service A in isolation, calling Service B directly would make the test flaky (since it depends on Service B), slow down execution, and be hard to control.

The solution? If we can mock the gRPC server, then we can:

  • Get responses that match the scenarios we want (happy/edge cases).
  • Measure how much of the logic in Service A is covered without depending on Service B.
  • Run tests faster and more reliably.

An Overview of the Testing Flow with Mocks

Using mocks with gRPC can be described with a simple flow like the following:

MERMAID
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

In the diagram above, the TestCase builds a mock server and then injects it into Service A. Every time Service A makes a request, the mock server responds according to what was configured in the test scenario.


Project Structure

To make it more realistic, I’ll use a simple case study:

  1. Define the gRPC protoc file (for example, a UserService)
  2. Generate Go code from the Protobuf
  3. Create a mock interface with gomock
  4. Implement a unit test using the mock server

Here is an example of a simple directory structure:

text
1.
2├── proto/
3│   └── user.proto
4├── pb/
5│   └── user.pb.go
6├── internal/
7│   ├── service.go
8│   └── service_test.go
9└── go.mod

1. Define the Protobuf File

For example, the file proto/user.proto:

proto
 1syntax = "proto3";
 2package pb;
 3
 4service UserService {
 5  rpc GetUser(GetUserRequest) returns (GetUserResponse);
 6}
 7
 8message GetUserRequest {
 9  string id = 1;
10}
11
12message GetUserResponse {
13  string id = 1;
14  string name = 2;
15}

2. Generate the gRPC Stub

Generate the Go code:

sh
1protoc --go_out=pb --go-grpc_out=pb proto/user.proto

As a result, the pb/ folder will contain user.pb.go and user_grpc.pb.go, which hold the client & server interfaces (for UserService):

go
1// The server interface that will be mocked
2type UserServiceServer interface {
3    GetUser(context.Context, *GetUserRequest) (*GetUserResponse, error)
4}

3. Generate the Mock Interface with gomock

Install mockgen from gomock if you don’t have it yet:

sh
1go install github.com/golang/mock/mockgen@latest

Generate the mock for the server interface:

sh
1mockgen -source=pb/user_grpc.pb.go -destination=internal/mock_user.go -package=internal

The file internal/mock_user.go will contain the MockUserServiceServer struct if you want to mock the server.


4. Implement a Unit Test with the Mock

Suppose we have the following logic in internal/service.go:

go
 1package internal
 2
 3import (
 4    "context"
 5    "pb"
 6)
 7
 8type Handler struct {
 9    userService pb.UserServiceClient
10}
11
12func NewHandler(client pb.UserServiceClient) *Handler {
13    return &Handler{userService: client}
14}
15
16func (h *Handler) FindUser(ctx context.Context, id string) (string, error) {
17    resp, err := h.userService.GetUser(ctx, &pb.GetUserRequest{Id: id})
18    if err != nil {
19        return "", err
20    }
21    return resp.Name, nil
22}

Testing Simulation: Mocking the gRPC Server Client

The unit test scenario:

  • We want to make sure that when the FindUser service calls GetUser, we can control the response (for example, name=“John Doe”) and test our logic without actually calling the real server.

Here is an example of the code in internal/service_test.go:

go
 1package internal
 2
 3import (
 4    "context"
 5    "testing"
 6
 7    "github.com/golang/mock/gomock"
 8    "pb"
 9)
10
11func TestFindUser(t *testing.T) {
12    ctrl := gomock.NewController(t)
13    defer ctrl.Finish()
14
15    mockUserSrv := NewMockUserServiceClient(ctrl)
16
17    // Setup mock: whenever GetUser is called with id '123', return John Doe
18    mockUserSrv.EXPECT().
19        GetUser(gomock.Any(), &pb.GetUserRequest{Id: "123"}).
20        Return(&pb.GetUserResponse{Id: "123", Name: "John Doe"}, nil)
21
22    handler := NewHandler(mockUserSrv)
23
24    name, err := handler.FindUser(context.Background(), "123")
25    if err != nil {
26        t.Fatalf("unexpected error: %v", err)
27    }
28    if name != "John Doe" {
29        t.Fatalf("expected John Doe, got %v", name)
30    }
31}

Table: Comparing Testing Without a Mock vs. With a Mock

MethodAdvantagesDisadvantages
Without a mockReal end-to-end, catches bugs between servicesSlow, fragile, hard to set up
With gomockFast, isolated, deterministicDoesn’t cover real server bugs

Best Practices for Mocking gRPC with gomock

  1. Mock only what’s necessary
    Mock only the behavior that is a dependency, not the SUT (System Under Test) itself.

  2. Use .EXPECT() as expressively as possible
    To know exactly which parameters are used and how many times something is called, use .AnyTimes(), .Times(), or other matchers from gomock.

  3. Separate mock logic into helper functions
    If a test is fairly complex, reuse the mock setup in a validation helper for cleaner code.


When Is Mocking the Server Better Than an Integration Test?

Use a mock when:

  • Writing unit tests for a logic component that interacts with a dependency via gRPC.
  • You want fast and repeatable tests.

Use a real integration test when:

  • You want to make sure the services are truly compatible with each other.
  • You are getting close to staging/production.

Conclusion

Mocking a gRPC server with gomock makes the Go testing workflow far more robust, fast, and maintainable. By generating mocks from the Protobuf interface, developers can easily test the capabilities of the client/service layer without worrying about the complexity and instability of an external server. This is a must-have skill for any Go engineer working with distributed systems.

References


Happy coding! Don’t hesitate to explore testing your distributed systems more flexibly using mocks!

Related Articles

💬 Comments