Home > Back-end >  How to mock a dummy grpc service with methods in Go
How to mock a dummy grpc service with methods in Go

Time:03-01

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
}
  • Related