英文:
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 接口。
你可以使用像 gomock 或 moq 这样的工具为任何 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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论