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

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

How to mock a dummy grpc service with methods in Go

问题

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

编辑:细节
源函数:

  1. func (s *mainType) MainGenerateBearerToken(inputs...)(output) {
  2. response, err := s.Subtype.SubGenerateBearerToken(context, authnPass, ttl)
  3. if err != nil {
  4. return nil, err
  5. }
  6. return &models.OCITokenResponse{Token: response.Token, ExpiresIn: ttl}, nil
  7. }
  8. func(cl *Subtype) SubGenerateBearerToken(context,authnPass, ttl) (*BearerTokenResponse, error) {
  9. resp,err := pb.NewGrpcClient(cl.Conn).GetBearerToken_grpc(ctx, &pb.GetBearerTokenRequest{AuthnToken, BearerTtlValue:ttlNum)
  10. return &BearerTokenResponse{Token}, err
  11. }

我正在为 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:

  1. func (s *mainType) MainGenerateBearerToken(inputs...)(output) {
  2. response, err := s.Subtype.SubGenerateBearerToken(context, authnPass, ttl)
  3. if err != nil {
  4. return nil, err
  5. }
  6. return &models.OCITokenResponse{Token: response.Token, ExpiresIn: ttl}, nil
  7. }
  8. func(cl *Subtype) SubGenerateBearerToken(context,authnPass, ttl) (*BearerTokenResponse, error) {
  9. resp,err := pb.NewGrpcClient(cl.Conn).GetBearerToken_grpc(ctx, &pb.GetBearerTokenRequest{AuthnToken, BearerTtlValue:ttlNum)
  10. return &BearerTokenResponse{Token}, err
  11. }

I'm writing test for the MainGenerateBearerToken() function, which calls SubGenerateBearerToken(), inside which the grpc method is called.

答案1

得分: 1

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

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

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

或者

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

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

使用 moq 的完整示例

my_service.proto:

  1. syntax = "proto3";
  2. package main;
  3. option go_package = "main";
  4. service MyService {
  5. rpc Hello(HelloMesage) returns (HelloMesage) {}
  6. }
  7. message HelloMesage {
  8. string msg = 1;
  9. }

main.go:

  1. package main
  2. import context "context"
  3. //go:generate protoc --go_out=plugins=grpc:. -I=. my_service.proto
  4. //go:generate moq -out my_service_mock.go . MyServiceClient
  5. func CallHello(myService MyServiceClient) (*HelloMesage, error) {
  6. return myService.Hello(context.TODO(), &HelloMesage{Msg: "hello"})
  7. }
  8. func main() {
  9. // (omitted) 正常调用 grpc ...
  10. }

main_test.go:

  1. package main
  2. import (
  3. context "context"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. grpc "google.golang.org/grpc"
  7. )
  8. func TestCallHello(t *testing.T) {
  9. myServiceClient := &MyServiceClientMock{
  10. HelloFunc: func(ctx context.Context, in *HelloMesage, opts ...grpc.CallOption) (*HelloMesage, error) {
  11. return &HelloMesage{Msg: in.Msg + " reply"}, nil
  12. },
  13. }
  14. res, err := CallHello(myServiceClient)
  15. assert.NoError(t, err)
  16. assert.Equal(t, "hello reply", res.Msg)
  17. }

运行:

  1. $ go mod init myservice
  2. $ go mod tidy
  3. $ go generate
  4. $ 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.

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

Or

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

  1. syntax = "proto3";
  2. package main;
  3. option go_package = "main";
  4. service MyService {
  5. rpc Hello(HelloMesage) returns (HelloMesage) {}
  6. }
  7. message HelloMesage {
  8. string msg = 1;
  9. }

main.go:

  1. package main
  2. import context "context"
  3. //go:generate protoc --go_out=plugins=grpc:. -I=. my_service.proto
  4. //go:generate moq -out my_service_mock.go . MyServiceClient
  5. func CallHello(myService MyServiceClient) (*HelloMesage, error) {
  6. return myService.Hello(context.TODO(), &HelloMesage{Msg: "hello"})
  7. }
  8. func main() {
  9. // (omitted) call grpc normally ...
  10. }

main_test.go:

  1. package main
  2. import (
  3. context "context"
  4. "testing"
  5. "github.com/stretchr/testify/assert"
  6. grpc "google.golang.org/grpc"
  7. )
  8. func TestCallHello(t *testing.T) {
  9. myServiceClient := &MyServiceClientMock{
  10. HelloFunc: func(ctx context.Context, in *HelloMesage, opts ...grpc.CallOption) (*HelloMesage, error) {
  11. return &HelloMesage{Msg: in.Msg + " reply"}, nil
  12. },
  13. }
  14. res, err := CallHello(myServiceClient)
  15. assert.NoError(t, err)
  16. assert.Equal(t, "hello reply", res.Msg)
  17. }

To run:

  1. $ go mod init myservice
  2. $ go mod tidy
  3. $ go generate
  4. $ go test

答案2

得分: 0

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

  1. type fakeClient struct {
  2. grpc.YourClient // 来自*.pb.go文件的客户端接口
  3. }
  4. func NewFakeClient(_ *grpc.ClientConn) *fakeClient {
  5. return &fakeClient{}
  6. }
  7. func (fc *fakeClient) GetBearerToken_grpc(ctx context.Context, in *pb.GetBearerTokenRequest, opts ...grpc.CallOption) (*pb.BearerTokenResponse, error) {
  8. return &pb.BearerTokenResponse{
  9. // 你的假响应...
  10. }, nil
  11. }
英文:

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:

  1. type fakeClient struct {
  2. grpc.YourClient // a client interface from your *.pb.go file
  3. }
  4. func NewFakeClient(_ *grpc.ClientConn) *fakeClient {
  5. return &fakeClient{}
  6. }
  7. func (fc *fakeClient) GetBearerToken_grpc(ctx context.Context, in *pb.GetBearerTokenRequest, opts ...grpc.CallOption) (*pb.BearerTokenResponse, error) {
  8. return &pb.BearerTokenResponse{
  9. // your fake response...
  10. }, nil
  11. }

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:

确定