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

jos*_*eph 7 java mockito grpc

我想模拟我的 grpc 客户端,以确保它通过抛出一个new StatusRuntimeException(Status.UNAVAILABLE)(这是java.net.ConnectException: Connection refused向 grpc 客户端抛出的异常)来应对失败。但是,生成的类是最终的,因此模拟将不起作用。

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

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

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

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


        blahServiceBlockingStub.blah(null);
    }
Run Code Online (Sandbox Code Playgroud)

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

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)
.
.
.
Run Code Online (Sandbox Code Playgroud)

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

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

Eri*_*son 9

不要模拟客户端存根或任何其他最终类/方法。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);
}
Run Code Online (Sandbox Code Playgroud)

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

  • 不过,这种方法是一种比模拟更冗长、更难阅读的操作顺序。是否有任何库可以帮助一般地提供请求/响应解决方案? (4认同)

Woj*_*tek 5

您有几个选择:

请注意为什么在这种情况下模拟 Final 可能是一个坏主意: 根据情况,模拟 Final 类或方法可能是一个坏主意。细节决定成败。在您的情况下,您正在创建生成的代码的模拟,因此您假设生成的代码将来的行为方式。gRPC 和 Protobuf 仍在快速发展,因此做出这些假设可能存在风险,因为它们可能会发生变化,而您不会注意到,因为您没有根据生成的代码检查模拟。因此,除非确实需要,否则模拟生成的代码并不是一个好主意。