16

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);
    }
joseph
  • 2,429
  • 1
  • 22
  • 43
  • As a side note, we need a special configuration for mocking final classes using the Mockito. This is described here for how we can approach that: https://www.baeldung.com/mockito-final – Arefe Feb 01 '22 at 10:20

4 Answers4

17

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.

Eric Anderson
  • 24,057
  • 5
  • 55
  • 76
  • I am trying this now. I will consult the route guide example you provided if I get stuck. I will update with my result in a comment here. This is much cleaner because I don't have to spy() the class I'm unit testing anymore in my workaround. – joseph Dec 31 '19 at 15:49
  • I used your sample to browse our code base. We have a BlahMoles generated by protoc-grpc-moles compiler plugin for unit test. That generated code has the same code as your sample: `responseObserver.onError(error.get())`. I would not have been able to find it without your answer. – joseph Dec 31 '19 at 18:13
  • I added `private BlahServiceMole blahBackend = spy(BlahServiceMole.class);` (BlahServiceMole generated by `protoc-grpc-moles compiler plugin`) and then in my `@Test` method I added `doReturn(Optional.of(new StatusRuntimeException(Status.UNAVAILABLE))).when(blahBackend).blahError(any());`. From there the mole translated blahError to onError in function `blah(input, responseObserver)` – joseph Dec 31 '19 at 18:31
  • 3
    This approach is an order of manitude more verbose and harder to read than a mock though. Are there any libraries to help generically provide request/response solutions trivially? – Kurru Apr 10 '21 at 20:17
  • "How do I write a pure unit test of a class using a GRPC client?" ... answer "mock the server. " I think we missed the point. We don't want to test ANY of the GRPC code in this unit test - just confirm that the code-under-test makes the expected calls to the client. – ash Feb 10 '23 at 23:03
5

You have a few options:

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.

Wojtek
  • 1,410
  • 2
  • 16
  • 31
1

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

Willem
  • 992
  • 6
  • 13
  • Yeah that looks like it will work. However my project is stuck on 1.10.19 which I cannot change. I don't have the authority to migrate to Mockito 2. – joseph Dec 30 '19 at 21:01
  • _Please_ do not mock final methods and classes. Mocks are brittle enough already. Mocking final methods/classes produce "impossible" results that will break things (if not today, then tomorrow). grpc-java has made many things final _to prevent_ them from being mocked. – Eric Anderson Dec 30 '19 at 21:15
1

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(); 
    }
joseph
  • 2,429
  • 1
  • 22
  • 43