如何使用Mockito模拟grpc ServiceBlockingStub引发StatusRuntimeException(Status.UNAVAILABLE)?

时间:2019-12-30 20:46:28

标签: java mockito grpc

我想模拟我的grpc客户端,以确保它可以通过抛出new StatusRuntimeException(Status.UNAVAILABLE)来抵抗故障(这是向java.net.ConnectException: Connection refused抛出grpc客户端时抛出的异常)。但是,生成的类是最终的,因此模拟无法正常工作。

如何使BlahServiceBlockingStub抛出new StatusRuntimeException(Status.UNAVAILABLE)而不必重构代码以围绕BlahServiceBlockingStub创建包装器类?

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

    @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)
.
.
.

因为我尝试模拟grpc生成的最终类:

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

4 个答案:

答案 0 :(得分:3)

您有几种选择:

请注意,为什么在这种情况下嘲笑final可能不是一个好主意: 根据具体情况,模拟最终类或方法可能不是一个好主意。细节决定成败。根据您的情况,您正在创建生成代码的模拟,因此您假设该生成代码在将来的行为。 gRPC和Protobuf仍在快速发展,因此做出这些假设可能会冒险,因为它们可能会更改,您不会注意到,因为您没有针对生成的代码检查模拟。因此,除非确实需要,否则模拟生成的代码不是一个好主意。

答案 1 :(得分:2)

请勿嘲笑客户端存根或任何其他最终类/方法。 gRPC团队可能会竭尽全力破坏您对此类模拟的使用,因为它们非常脆弱,并且会产生“不可能”的结果。

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

-Dprofile=prod

进行多个测试时,您可以将服务器,通道和存根的创建提升到各个测试中的字段或@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); } 中。这样做时,可以方便地在服务器上将@Before用作MutableHandlerRegistry。这样,您就可以在服务器启动后注册服务。有关该方法的完整示例,请参见route guide example

答案 2 :(得分:0)

如何使用Mockito模拟最终课程/方法:

添加依赖项Mockito Inline

创建文件src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

仅一行:mock-maker-inline

现在您可以模拟最终方法和类。

Mockito docs about mocking

答案 3 :(得分:0)

我最终遇到了一个丑陋的解决方法。

我在类上创建了一个新方法,并且在类中引用了BlahServiceBlockingStub。

生成的代码最终看起来像:

spy()