如何使用Go语言模拟一个带有方法的虚拟gRPC服务

huangapple go评论84阅读模式
英文:

How to mock a dummy grpc service with methods in Go

问题

我需要为一个函数编写单元测试,该函数在其源代码中调用了一个不同的 gRPC 服务的 gRPC 方法。我的测试结果出现了 panic 错误,因为该 gRPC 服务没有在我的机器上运行。我想在我的测试中创建一个虚拟服务,并且该服务必须提供源代码中调用的方法。我该如何做?

编辑:细节
源函数:

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
}

我正在为 MainGenerateBearerToken() 函数编写测试,该函数调用 SubGenerateBearerToken(),而 SubGenerateBearerToken() 中调用了 gRPC 方法。

英文:

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.

答案1

得分: 1

一个 gRPC 服务客户端是一个普通的 Go 接口。

你可以使用像 gomockmoq 这样的工具为任何 Go 接口生成模拟。

$ go install github.com/golang/mock/mockgen@v1.6.0
$ mockgen -source=my_service.pb.go MyServiceClient

或者

$ go install github.com/matryer/moq@v0.2.5
$ moq -pkg mock -out mock/my_service.go . MyServiceClient

这将生成一个实现 MyServiceClient 接口的 mock.MyServiceClientMock。然后你可以在单元测试中使用它,而不是真正的 gRPC 客户端。

使用 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) 正常调用 grpc ...
}

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)

}

运行:

$ go mod init myservice
$ go mod tidy
$ go generate
$ go test
英文:

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/mockgen@v1.6.0
$ mockgen -source=my_service.pb.go MyServiceClient

Or

$ go install github.com/matryer/moq@v0.2.5
$ 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

答案2

得分: 0

如果在SubGenerateBearerToken方法中删除grpc客户端的创建是合适的(例如,将其保留在子类型中或作为参数传递),你可以轻松地创建一个简单的假客户端:

type fakeClient struct {
    grpc.YourClient // 来自*.pb.go文件的客户端接口
}

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{
        // 你的假响应...
    }, nil
}
英文:

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
}

huangapple
  • 本文由 发表于 2022年2月28日 20:47:15
  • 转载请务必保留本文链接:https://go.coder-hub.com/71295259.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定