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
Here is possibly a simpler way of just testing a streaming service. Apologies if there are any typo's as I am adapting this from some running code.
Given the following definition.
rpc ListSites(Filter) returns(stream sites)
With the following server side code.
// ListSites ...
func (s *SitesService) ListSites(filter *pb.SiteFilter, stream pb.SitesService_ListSitesServer) error {
for _, site := range s.sites {
if err := stream.Send(site); err != nil {
return err
}
}
return nil
}
Now all you have to do is mock the pb.SitesService_ListSitesServer in your tests file.
type mockSiteService_ListSitesServer struct {
grpc.ServerStream
Results []*pb.Site
}
func (_m *mockSiteService_ListSitesServer) Send(site *pb.Site) error {
_m.Results = append(_m.Results, site)
return nil
}
This responds to the .send event and records the sent objects in .Results which you can then use in your assert statements.
Finally you call the server code with the mocked immplementation of pb.SitesService_ListSitesServer.
func TestListSites(t *testing.T) {
s := SiteService.NewSiteService()
filter := &pb.SiteFilter{}
mock := &mockSiteService_ListSitesServer{}
s.ListSites(filter, mock)
assert.Equal(t, 1, len(mock.Results), "Sites expected to contain 1 item")
}
No it doesn't test the entire stack but it does allow you to sanity check your server side code without the hassle of running up a full gRPC service either for real or in mock form.
As a new contributor, I can not comment so I am adding here as an answer.
The @shiblon answer is the best way to test your service. I am the maintainer of the grpc-for-production and one of the features is an in processing server which makes it easier to work with bufconn.
Here one example of testing the greeter service
var server GrpcInProcessingServer
func serverStart() {
builder := GrpcInProcessingServerBuilder{}
builder.SetUnaryInterceptors(util.GetDefaultUnaryServerInterceptors())
server = builder.Build()
server.RegisterService(func(server *grpc.Server) {
helloworld.RegisterGreeterServer(server, &testdata.MockedService{})
})
server.Start()
}
//TestSayHello will test the HelloWorld service using A in memory data transfer instead of the normal networking
func TestSayHello(t *testing.T) {
serverStart()
ctx := context.Background()
clientConn, err := GetInProcessingClientConn(ctx, server.GetListener(), []grpc.DialOption{})
if err != nil {
t.Fatalf("Failed to dial bufnet: %v", err)
}
defer clientConn.Close()
client := helloworld.NewGreeterClient(clientConn)
request := &helloworld.HelloRequest{Name: "test"}
resp, err := client.SayHello(ctx, request)
if err != nil {
t.Fatalf("SayHello failed: %v", err)
}
server.Cleanup()
clientConn.Close()
assert.Equal(t, resp.Message, "This is a mocked service test")
}
You can find this example here
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.
There are many ways you can choose to test a gRPC service. You may choose to test in different ways depending on the kind of confidence you would like to achieve. Here are three cases that illustrate some common scenarios.
In this case you are interested in the logic in the service and how it interacts with other components. The best thing to do here is write some unit tests.
There is a good introduction to unit testing in Go by Alex Ellis. If you need to test interactions then GoMock is the way to go. Sergey Grebenshchikov wrote a nice GoMock tutorial.
The answer from Omar shows how you could approach unit testing this particular SayHello
example.
In this case you are interested in doing manually exploratory testing of your API. Typically this is done to explore the implementation, check edge cases and gain confidence that your API behaves as expected.
You will need to:
Now you can use your mocking solution to simulate real and hypothetical situations while observing the behaviour on the service under test by using the API testing tool.
In this case you are interested in writing automated BDD style acceptance tests that interact with the system under test via the over the wire gRPC API. These tests are expensive to write, run and maintain and should be used sparingly, keeping in mind the testing pyramid.
The answer from thinkerou shows how you can use karate-grpc to write those API tests in Java. You can combine this with the Traffic Parrot Maven plugin to mock any over the wire dependencies.
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.
I came up with the following implementation which may not be the best way of doing it. Mainly using the TestMain
function to spin up the server using a goroutine like that:
const (
port = ":50051"
)
func Server() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
func TestMain(m *testing.M) {
go Server()
os.Exit(m.Run())
}
and then implement the client in the rest of the tests:
func TestMessages(t *testing.T) {
// Set up a connection to the Server.
const address = "localhost:50051"
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
t.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Test SayHello
t.Run("SayHello", func(t *testing.T) {
name := "world"
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
t.Fatalf("could not greet: %v", err)
}
t.Logf("Greeting: %s", r.Message)
if r.Message != "Hello "+name {
t.Error("Expected 'Hello world', got ", r.Message)
}
})
}