Mastering Unary gRPC Implementation with Golang and Unit Tests
Written on
Building a Unary gRPC Service
In this guide, we will delve into the implementation of a unary gRPC service using Golang, complete with unit testing and manual testing via grpcui.
Unary gRPC Overview
Unary gRPC represents the most basic communication method in gRPC, where a client sends a single request to the server and receives a singular response. This approach is particularly useful when clients need to transmit limited data to the server.
Creating a Unary gRPC Service
Let's develop a unary gRPC service that retrieves contact names by their numbers, and vice versa, along with a method to fetch all contacts. Before we start coding, we need to create a protocol buffer (proto) for our service.
- Folder Setup: Begin by creating a folder named unary. The term unary signifies the type of gRPC. You can choose any name you prefer.
- Directory Structure: Inside the unary folder, create three subfolders: proto, client, and server.
- Proto File Creation: In the proto folder, create a file named unary.proto and populate it with the necessary content. The structure of this file is detailed below, including three different request examples.
syntax = "proto3";
option go_package = "alltypes/unary/proto";
package pb;
// Model
message Contacts {
string firstname = 1;
string lastname = 2;
uint64 number = 3;
}
// Unary services
service Phone {
rpc GetContactName(GetContactNameRequest) returns (GetContactNameResponse) {}
rpc GetContactNum(GetContactNumRequest) returns (GetContactNumResponse) {}
rpc ListContacts(ListContactsRequest) returns (ListContactsResponse) {}
}
message GetContactNameRequest {
string number = 1;
}
message GetContactNameResponse {
string firstname = 1;
string lastname = 2;
}
message GetContactNumRequest {
string firstname = 1;
string lastname = 2;
}
message GetContactNumResponse {
uint64 num = 1;
string result = 2;
}
message ListContactsRequest {}
message ListContactsResponse {
int32 sum = 1;
repeated Contacts contacts = 2;
}
- Generating pb.go Files: Use the following command in your terminal to automatically generate the pb.go files.
protoc --go_out=. --go-grpc_opt=require_unimplemented_servers=false --go_opt=paths=source_relative
--go-grpc_out=. --go-grpc_opt=paths=source_relative
proto/unary.proto
Once you execute this command, two files, unary.pb.go and unary_grpc.pb.go, will be generated. Avoid altering these files directly; instead, modify the unary.proto file and run the command again if necessary.
- Creating the Server: Now, let's build the server. Inside the server folder, create a file named server.go and implement the following code.
package main
import (
"context"
"errors"
"log"
"net"
"strconv"
unary "github.com/ramseyjiang/go_mid_to_senior/pkgusages/grpc/alltypes/unary/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
)
type phoneServer struct {
unary.PhoneServer
}
func main() {
listener, err := net.Listen("tcp", "0.0.0.0:50055")
if err != nil {
_ = errors.New("failed to listen: the port")}
log.Print("Server started")
s := grpc.NewServer()
unary.RegisterPhoneServer(s, &phoneServer{})
// Register reflection service on gRPC server.
reflection.Register(s)
if err = s.Serve(listener); err != nil {
log.Fatalf("failed to serve: %v", err)}
}
The above implementation sets up a gRPC server that listens on port 50055.
Unit Testing the Unary gRPC Server
To verify that your server is functioning correctly, we can implement unit tests. Create a file named server_test.go in the server folder and populate it with the following code:
package main
import (
"context"
"errors"
"log"
"testing"
unary "github.com/ramseyjiang/go_mid_to_senior/pkgusages/grpc/alltypes/unary/proto"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/test/bufconn"
)
// Server setup function for testing
func server(ctx context.Context) (client unary.PhoneClient, closer func()) {
buffer := 101024 * 1024
lis := bufconn.Listen(buffer)
baseServer := grpc.NewServer()
unary.RegisterPhoneServer(baseServer, &phoneServer{})
go func() {
if err := baseServer.Serve(lis); err != nil {
log.Printf("error serving server: %v", err)}
}()
cc, err := grpc.Dial("localhost:50055", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Printf("error connecting to server: %v", err)}
closer = func() {
err = lis.Close()
if err != nil {
log.Printf("error closing listener: %v", err)}
baseServer.Stop()
}
client = unary.NewPhoneClient(cc)
return
}
This setup enables you to create tests for your gRPC methods, ensuring they return the expected results.
Testing the Server with grpcui
After starting your server, you can manually test it using grpcui. Open a new terminal and run:
grpcui -plaintext 0.0.0.0:50055
This command will launch a browser window displaying all implemented gRPC methods, allowing you to verify their functionality.
Conclusion
In this tutorial, we've successfully implemented a unary gRPC service in Golang, complete with unit tests and manual verification methods using grpcui. This should provide a solid foundation for building and testing gRPC services in your applications. Happy coding!
For further learning, consider reading these related articles:
- REST vs gRPC: Which One is More Suitable for MicroServices?
- Using Golang to build server streaming gRPC with unit tests
- Using Golang to build client streaming gRPC with unit tests
- Using Golang to build a chat service using bidirectional streaming gRPC with unit tests
- Using Golang to build TLS Unary gRPC