Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use mockito to mock grpc ServiceBlockingStub to throw StatusRuntimeException(Status.UNAVAILABLE)?

Tags:

java

mockito

grpc

I want to mock my grpc client to ensure that it is resilient to failure by throwing an new StatusRuntimeException(Status.UNAVAILABLE) (This is the exception that is thrown when java.net.ConnectException: Connection refused is thrown to the grpc client). However, the generated class is final, so mock will not work.

How do I get BlahServiceBlockingStub to throw new StatusRuntimeException(Status.UNAVAILABLE) without having to refactor my code to create a wrapper class around BlahServiceBlockingStub?

This is what I have tried (where BlahServiceBlockingStub was generated by grpc):

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

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


        blahServiceBlockingStub.blah(null);
    }

Unfortunately I get the below exception as expected:

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

Because I tried mocking the final class generated by grpc:

  public static final class BlahServiceBlockingStub extends io.grpc.stub.AbstractStub<BlahServiceBlockingStub> {
    private BlahServiceBlockingStub(io.grpc.Channel channel) {
      super(channel);
    }
like image 346
joseph Avatar asked Dec 30 '19 20:12

joseph


4 Answers

Do not mock the client stub, or any other final class/method. The gRPC team may go out of their way to break your usage of such mocks, as they are extremely brittle and can produce "impossible" results.

Mock the service, not the client stub. When combined with the in-process transport it produces fast, reliable tests. This is the same approach as demonstrated in the grpc-java hello world example.

@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);
}

When doing multiple tests, you can hoist the server, channel, and stub creation into fields or @Before, out of the individual tests. When doing that it can be convenient to use MutableHandlerRegistry as a fallbackHandlerRegistry() on the server. That allows you to register services after the server is started. See the route guide example for a fuller example of that approach.

like image 112
Eric Anderson Avatar answered Nov 18 '22 13:11

Eric Anderson


You have a few options:

  • NOT RECOMMENDED: Use Mockito v2 to mock final classes and methods.
  • NOT RECOMMENDED: Use powermock to mocks final classes and methods.
  • Recommended: (as already mentioned by Eric in his answer) Use the gRPC Java test framework like GrpcCleanupRule and InProcessServerBuilder. See HelloWorldClientTest for an example.
  • Recommended: Do over-the-wire gRPC API mocking/simulation. Use a third-party tool that will create over-the-wire API mocks/simulators for your API. For example, Traffic Parrot.

Note why mocking final, in this case, might be a bad idea: Mocking final classes or methods might be a bad idea, depending on the case. The devil is in the details. In your situation, you are creating a mock of the generated code, so you are assuming how that generated code will behave in the future. gRPC and Protobuf are still rapidly evolving, so it might be risky to make those assumptions, as they might change and you won't notice because you do not check your mocks against the generated code. Hence, it's not a good idea to mock the generated code unless you really have to.

like image 43
Wojtek Avatar answered Nov 18 '22 12:11

Wojtek


How to mock final classes/methods with mockito:

add dependency Mockito Inline

create file src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

with one line: mock-maker-inline

And now you can mock final methods and classes.

Mockito docs about mocking

like image 1
Willem Avatar answered Nov 18 '22 12:11

Willem


I ended up with an ugly workaround.

I created a new method and a spy() on the class that has a reference to BlahServiceBlockingStub.

The resulting code ended up looking like:

    @Test
    public void test() {
        MyClass myClass = spy(myClass);

        doThrow(new StatusRuntimeException(Status.UNAVAILABLE)).when(myClass).newMethod(any());

        // changed to call myClass.newMethod() instead of blahServiceBlockingStub.blah
        myClass.myExistingMethod(); 
    }
like image 1
joseph Avatar answered Nov 18 '22 11:11

joseph