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离散化传递函数与仿真不一致