Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing a gRPC service

Tags:

go

grpc

I'd like to test a gRPC service written in Go. The example I'm using is the Hello World server example from the grpc-go repo.

The protobuf definition is as follows:

syntax = "proto3";  package helloworld;  // The greeting service definition. service Greeter {   // Sends a greeting   rpc SayHello (HelloRequest) returns (HelloReply) {} }  // The request message containing the user's name. message HelloRequest {   string name = 1; }  // The response message containing the greetings message HelloReply {   string message = 1; } 

And the type in the greeter_server main is:

// server is used to implement helloworld.GreeterServer. type server struct{}  // SayHello implements helloworld.GreeterServer func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {     return &pb.HelloReply{Message: "Hello " + in.Name}, nil } 

I've looked for examples but I couldn't find any on how to implement tests for a gRPC service in Go.

like image 290
joscas Avatar asked Feb 08 '17 00:02

joscas


People also ask

How do I mock a gRPC service?

Mock a gRPC client proto file. For example, a service called Greeter generates a GreeterClient type with methods to call the service. A mocking framework can mock a gRPC client type. When a mocked client is passed to the type, the test uses the mocked method instead of sending a gRPC call to a server.

What is a service in gRPC?

gRPC is based on the idea of defining a service and the methods a client can use to operate the service. A gRPC service definition uses protocol buffers as its Interface Definition Language to describe the service. In this series, we will create a useful service to securely store usernames and passwords.


2 Answers

I think you're looking for the google.golang.org/grpc/test/bufconn package to help you avoid starting up a service with a real port number, but still allowing testing of streaming RPCs.

import "google.golang.org/grpc/test/bufconn"  const bufSize = 1024 * 1024  var lis *bufconn.Listener  func init() {     lis = bufconn.Listen(bufSize)     s := grpc.NewServer()     pb.RegisterGreeterServer(s, &server{})     go func() {         if err := s.Serve(lis); err != nil {             log.Fatalf("Server exited with error: %v", err)         }     }() }  func bufDialer(context.Context, string) (net.Conn, error) {     return lis.Dial() }  func TestSayHello(t *testing.T) {     ctx := context.Background()     conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(bufDialer), grpc.WithInsecure())     if err != nil {         t.Fatalf("Failed to dial bufnet: %v", err)     }     defer conn.Close()     client := pb.NewGreeterClient(conn)     resp, err := client.SayHello(ctx, &pb.HelloRequest{"Dr. Seuss"})     if err != nil {         t.Fatalf("SayHello failed: %v", err)     }     log.Printf("Response: %+v", resp)     // Test for output here. } 

The benefit of this approach is that you're still getting network behavior, but over an in-memory connection without using OS-level resources like ports that may or may not clean up quickly. And it allows you to test it the way it's actually used, and it gives you proper streaming behavior.

I don't have a streaming example off the top of my head, but the magic sauce is all above. It gives you all of the expected behaviors of a normal network connection. The trick is setting the WithDialer option as shown, using the bufconn package to create a listener that exposes its own dialer. I use this technique all the time for testing gRPC services and it works great.

like image 150
shiblon Avatar answered Sep 24 '22 04:09

shiblon


If you want to verify that the implementation of the gRPC service does what you expect, then you can just write standard unit tests and ignore networking completely.

For example, make greeter_server_test.go:

func HelloTest(t *testing.T) {     s := server{}      // set up test cases     tests := []struct{         name string         want string     } {         {             name: "world",             want: "Hello world",         },         {             name: "123",             want: "Hello 123",         },     }      for _, tt := range tests {         req := &pb.HelloRequest{Name: tt.name}         resp, err := s.SayHello(context.Background(), req)         if err != nil {             t.Errorf("HelloTest(%v) got unexpected error")         }         if resp.Message != tt.want {             t.Errorf("HelloText(%v)=%v, wanted %v", tt.name, resp.Message, tt.want)         }     } } 

I might've messed up the proto syntax a bit doing it from memory, but that's the idea.

like image 35
Omar Avatar answered Sep 23 '22 04:09

Omar