dousi6701 2019-08-27 19:42
浏览 162
已采纳

欺骗grpc UnaryHandler以在Go中对gRPC进行单元测试

I'm working on improving coverage for my Go gRPC server but I have run into trouble writing a test for the server's interceptor function because I'm unable to meaningfully satisfy the UnaryHandler type.

I have a function Interceptor with the following signature:

Interceptor func(
  ctx context.Context,
  req interface{},
  info *grpc.UnaryServerInfo,
  handler grpc.UnaryHandler, // <- my issue comes from here
) (interface{}, error)

I assumed that any gRPC method would satisfy the signature of UnaryHandler:

type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)

So I tried passing in a method with this signature:

GetToken(ctx context.Context, req *AuthData) (*Token, error)

I imagined this would work, since this is what the Interceptor is actually doing (forwarding that RPC), but for some reason Go complains:

cannot use authService.GetToken (type func(context.Context, *AuthData) (*Token, error)) as type grpc.UnaryHandler in argument to Interceptor

I went ahead and wrote a dummy function that correctly satisfies:

func genericHandler(ctx context.Context, req interface{}) (interface{}, error) {
    return req, nil
}

which is fine since I don't particularly need to run any specific method when testing the interceptor. However I am curious as to why the actual method doesn't satisfy the constraints because (according to my understanding) it is being passed to the Interceptor function under hood whenever I call that RPC in the wild.

The most likely explanation is that the grpc UnaryHandler doesn't do what I'm thinking it does, but then what does it do?

  • 写回答

1条回答 默认 最新

  • douchongbc72264 2019-09-01 09:52
    关注

    No, the function

    GetToken(ctx context.Context, req *AuthData) (*Token, error)
    

    is not of the same type as

    type UnaryHandler func(ctx context.Context, req interface{}) (interface{}, error)
    

    In GetToken the second parameter req is of type *AuthData, while in UnaryHandler req is of type interface{}. The returned *Token is not the same type as interface{}, neither. This is the reason why you can't pass GetToken directly to the interceptor.

    In your grpc services, you would write methods like

    GetToken(ctx context.Context, req *AuthData) (*Token, error)
    

    as handlers to do your server works. However, it's not an UnaryHandler as one may think.

    Most of the transformation is done by the grpc/protobuf code generator. Base on your proto definition, it generates an interface, like this:

    type XXXServer interface {
        GetToken(ctx context.Context, req *AuthData) (*Token, error)
    }
    

    You can see that it is this interface (not the UnaryHander) that your handler satisfies.

    Under the hood, if you take a look on the xxx.pb.go file generated, you'll find some _XXX_GetToken_Handler that's actually doing the handler work. In this function, an (actual) UnaryHandler is defined, as:

    func _XXX_GetToken_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
        // skip other preparations...
        // 
        handler := func(ctx context.Context, req interface{}) (interface{}, error) {
            return srv.(XXXServer).GetToken(ctx, req.(*AuthData))
        }
        return interceptor(ctx, in, info, handler)
    }
    

    Inside this UnaryHandler, it will cast your server to the XXXServer interface, and then invoke your handler (your code). And that shows how the interceptor is called.

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论

报告相同问题?

悬赏问题

  • ¥30 这是哪个作者做的宝宝起名网站
  • ¥60 版本过低apk如何修改可以兼容新的安卓系统
  • ¥25 由IPR导致的DRIVER_POWER_STATE_FAILURE蓝屏
  • ¥50 有数据,怎么建立模型求影响全要素生产率的因素
  • ¥50 有数据,怎么用matlab求全要素生产率
  • ¥15 TI的insta-spin例程
  • ¥15 完成下列问题完成下列问题
  • ¥15 C#算法问题, 不知道怎么处理这个数据的转换
  • ¥15 YoloV5 第三方库的版本对照问题
  • ¥15 请完成下列相关问题!