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.

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

报告相同问题?

悬赏问题

  • ¥15 通过kinect制作换装程序但是服装不贴合(标签-ar)
  • ¥20 matlab如何绘制三维瀑布图
  • ¥15 关于用abap来解决动态规划的问题,但是要求输出索引值,这个是难点
  • ¥15 在ISIS中什么是IP从地址
  • ¥15 压测时,并发量过高时,响应时间出现尖刺
  • ¥15 关于vmprotect3.8.4虚拟文件一项
  • ¥15 在不用IT调试的情况下怎样能连外网
  • ¥20 C#调用虚拟键盘TabTip.exe
  • ¥15 Qt4代码实现下面的界面
  • ¥15 CCS离散化传递函数与仿真不一致