如何使用 mockito 来模拟 grpc ServiceBlockingStub 来抛出 StatusRuntimeException(Status.UNAVAILABLE)?

2022-09-04 19:31:44

我想模拟我的 grpc 客户端,以确保它通过抛出一个 (这是抛出到 grpc 客户端时引发的异常)来恢复故障。但是,生成的类是最终的,因此 mock 将不起作用。new StatusRuntimeException(Status.UNAVAILABLE)java.net.ConnectException: Connection refused

如何让 BlahServiceBlockingStub 抛出,而不必重构我的代码来围绕 BlahServiceBlockingStub 创建一个包装类?new StatusRuntimeException(Status.UNAVAILABLE)

这就是我尝试过的(BlahServiceBlockingStub是由gpc生成的):

    @Test
    public void test() {
        BlahServiceBlockingStub blahServiceBlockingStub = mock(BlahServiceBlockingStub.class);

        when(blahServiceBlockingStub.blah(any())).thenThrow(new StatusRuntimeException(Status.UNAVAILABLE));


        blahServiceBlockingStub.blah(null);
    }

不幸的是,我得到了以下异常,如预期:

org.mockito.exceptions.base.MockitoException: 
Cannot mock/spy class BlahServiceGrpc$BlahServiceBlockingStub
Mockito cannot mock/spy following:
  - final classes
  - anonymous classes
  - primitive types

    at MyTestClass.test(MyTestClass.java:655)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
.
.
.

因为我试图嘲笑gpc生成的最终类:

  public static final class BlahServiceBlockingStub extends io.grpc.stub.AbstractStub<BlahServiceBlockingStub> {
    private BlahServiceBlockingStub(io.grpc.Channel channel) {
      super(channel);
    }

答案 1

不要模拟客户端存根或任何其他最终类/方法。gRPC团队可能会不遗余力地打破您对此类模拟的使用,因为它们非常脆弱,可以产生“不可能”的结果。

模拟服务,而不是客户端存根。当与过程中的运输相结合时,它可以产生快速,可靠的测试。这与 grpc-java hello world 示例中演示的方法相同。

@Rule
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();

@Test
public void test() {
    // This can be a mock, but is easier here as a fake implementation
    BlahServiceImplBase serviceImpl = new BlahServiceImplBase() {
        @Override public void blah(Request req, StreamObserver<Response> resp) {
            resp.onError(new StatusRuntimeException(Status.UNAVAILABLE));
        }
    };
    // Note that the channel and server can be created in any order
    grpcCleanup.register(InProcessServerBuilder.forName("mytest")
        .directExecutor().addService(serviceImpl).build().start());
    ManagedChannel chan = grpcCleanup.register(
        InProcessChannelBuilder.forName("mytest").directExecutor().build();
    BlahServiceBlockingStub blahServiceBlockingStub
        = BlahServiceGrpc.newBlockingStub();

    blahServiceBlockingStub.blah(null);
}

执行多个测试时,可以将服务器、通道和存根创建提升到各个测试的字段或 中。这样做时,可以方便地用作服务器上的。这允许您在服务器启动后注册服务。有关该方法的更完整示例,请参阅路由指南示例。@BeforeMutableHandlerRegistryfallbackHandlerRegistry()


答案 2

您有几种选择:

请注意,为什么在这种情况下,嘲笑最终版可能是一个坏主意:嘲笑最终的类或方法可能是一个坏主意,具体取决于具体情况。细节决定成败。在您的情况下,您正在创建所生成代码的模拟,因此您假设生成的代码将来将如何运行。gRPC和Protobuf仍在迅速发展,因此做出这些假设可能会有风险,因为它们可能会改变,并且您不会注意到,因为您没有根据生成的代码检查模拟。因此,除非您真的必须这样做,否则模拟生成的代码不是一个好主意。


推荐