I need to write unit test for a function, which in its source code calling a gRPC method of a different gRPC service. My test result in panic error because that gRPC service is not running on my machine. I want to create a dummy service in my test and that service must serve the method calling inside the source code. How do I do that?
Edit: Details Source function:
func (s *mainType) MainGenerateBearerToken(inputs...)(output) {
response, err := s.Subtype.SubGenerateBearerToken(context, authnPass, ttl)
if err != nil {
return nil, err
}
return &models.OCITokenResponse{Token: response.Token, ExpiresIn: ttl}, nil
}
func(cl *Subtype) SubGenerateBearerToken(context,authnPass, ttl) (*BearerTokenResponse, error) {
resp,err := pb.NewGrpcClient(cl.Conn).GetBearerToken_grpc(ctx, &pb.GetBearerTokenRequest{AuthnToken, BearerTtlValue:ttlNum)
return &BearerTokenResponse{Token}, err
}
I'm writing test for the MainGenerateBearerToken() function, which calls SubGenerateBearerToken(), inside which the grpc method is called.
CodePudding user response:
A gRPC service client is a regular Go interface.
You can use tools like gomock or moq to generate a mock for any Go interface.
$ go install github.com/golang/mock/[email protected]
$ mockgen -source=my_service.pb.go MyServiceClient
Or
$ go install github.com/matryer/[email protected]
$ moq -pkg mock -out mock/my_service.go . MyServiceClient
That will generate mock.MyServiceClientMock
that implements MyServiceClient
. Then you can use it in your unit tests instead of the real gRPC client.
Complete example using moq:
my_service.proto
:
syntax = "proto3";
package main;
option go_package = "main";
service MyService {
rpc Hello(HelloMesage) returns (HelloMesage) {}
}
message HelloMesage {
string msg = 1;
}
main.go
:
package main
import context "context"
//go:generate protoc --go_out=plugins=grpc:. -I=. my_service.proto
//go:generate moq -out my_service_mock.go . MyServiceClient
func CallHello(myService MyServiceClient) (*HelloMesage, error) {
return myService.Hello(context.TODO(), &HelloMesage{Msg: "hello"})
}
func main() {
// (omitted) call grpc normally ...
}
main_test.go
:
package main
import (
context "context"
"testing"
"github.com/stretchr/testify/assert"
grpc "google.golang.org/grpc"
)
func TestCallHello(t *testing.T) {
myServiceClient := &MyServiceClientMock{
HelloFunc: func(ctx context.Context, in *HelloMesage, opts ...grpc.CallOption) (*HelloMesage, error) {
return &HelloMesage{Msg: in.Msg " reply"}, nil
},
}
res, err := CallHello(myServiceClient)
assert.NoError(t, err)
assert.Equal(t, "hello reply", res.Msg)
}
To run:
$ go mod init myservice
$ go mod tidy
$ go generate
$ go test
CodePudding user response:
If it's appropriate to get rid of the grpc client creation in the SubGenerateBearerToken method (e.g. keep it in the Subtype or pass as an arg) you could easily create a simple fake client:
type fakeClient struct {
grpc.YourClient // a client interface from your *.pb.go file
}
func NewFakeClient(_ *grpc.ClientConn) *fakeClient {
return &fakeClient{}
}
func (fc *fakeClient) GetBearerToken_grpc(ctx context.Context, in *pb.GetBearerTokenRequest, opts ...grpc.CallOption) (*pb.BearerTokenResponse, error) {
return &pb.BearerTokenResponse{
// your fake response...
}, nil
}