Mock gRPC Services for Unit Testing

In our day-to-day work, we develop applications that involve interacting with software components through I/O. They can be a database, a broker, or some type of blob storage. For example, take the cloud components you interact with: Azure Storage Queue, SQS, Pub/Sub. Communication with those components is usually with an SDK.

From the beginning, the test will start. Therefore, interactions with those components must be dealt with in the context of testing. One approach is to use installations (or simulators) of those components and have the code interact with the actual instance, in much the same way that can be achieved by using test containers or simply building the infrastructure for testing purposes. .
Another approach is to spin up a mock service of components and have tests interacting with it. A good example of this might be the hoverfly. During testing a mock HTTP service is run and the test cases interact with it.

Both can be used in a variety of situations depending on the properties required for our testing process. We will focus on the second approach applied to gRPC.

It is well known that most of the Google Cloud components come with the gRPC API. In our scenario, we have an application publishing messages to pub/sub.

Let’s put the required dependencies first:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>com.google.cloud</groupId>
            <artifactId>libraries-bom</artifactId>
            <version>24.1.2</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
 
<dependencies>
    <dependency>
        <groupId>com.google.cloud</groupId>
        <artifactId>google-cloud-pubsub</artifactId>
    </dependency>
    <dependency>
        <groupId>io.grpc</groupId>
        <artifactId>grpc-testing</artifactId>
        <version>1.43.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.google.api.grpc</groupId>
        <artifactId>grpc-google-cloud-pubsub-v1</artifactId>
        <version>1.97.1</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Let’s start with our publisher class.

package com.egkatzioura.notification.publisher; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutureCallback; import com.google.api.core.ApiFutures; import com.google.cloud.pubsub.v1.Publisher; import com.google.protobuf.ByteString; import com.google.pubsub.v1.PubsubMessage; public class UpdatePublisher { private final Publisher publisher; private final Executor executor; public UpdatePublisher(Publisher publisher, Executor executor) { this.publisher = publisher; this.executor = executor; } public CompletableFuture<String> update(String notification) { PubsubMessage pubsubMessage = PubsubMessage.newBuilder() .setData(ByteString.copyFromUtf8(notification)) .build(); ApiFuture<String> apiFuture = publisher.publish(pubsubMessage); return toCompletableFuture(apiFuture); } private CompletableFuture<String> toCompletableFuture(ApiFuture<String> apiFuture) { final CompletableFuture<String> responseFuture = new CompletableFuture<>(); ApiFutures.addCallback(apiFuture, new ApiFutureCallback<>() { @Override public void onFailure(Throwable t) { responseFuture.completeExceptionally } @Override public void onSuccess(String result) { responseFuture.complete(result); } }, executor); return responseFuture; } }

The publisher will send the message and return the CompletableFuture of the message ID that was sent.
So let’s test this class. Our goal is to send a message and get the message ID back. The service to mock and emulate is pub/sub.

For this purpose, we added gRPC API dependency on Maven.

<dependency>
    <groupId>com.google.api.grpc</groupId>
    <artifactId>grpc-google-cloud-pubsub-v1</artifactId>
    <version>1.97.1</version>
    <scope>test</scope>
</dependency>

We will mock the API for publish action. The implementing class is PublisherGrpc.PublisherImplBase.

package com.egkatzioura.notification.publisher;
 
import java.util.UUID;
 
import com.google.pubsub.v1.PublishRequest;
import com.google.pubsub.v1.PublishResponse;
import com.google.pubsub.v1.PublisherGrpc;
 
import io.grpc.stub.StreamObserver;
 
public class MockPublisherGrpc extends PublisherGrpc.PublisherImplBase {
 
    private final String prefix;
 
    public MockPublisherGrpc(String prefix) {
        this.prefix = prefix;
    }
 
    @Override
    public void publish(PublishRequest request, StreamObserver<PublishResponse> responseObserver) {
        responseObserver.onNext(PublishResponse.newBuilder().addMessageIds(prefix+":"+UUID.randomUUID().toString()).build());
        responseObserver.onCompleted();
    }
 
}

As you see, the message id will have a prefix that we define.

This would be the PublisherGrpc implementation on the server side. Let us move on to our unit testing. A Publisher can be injected into the UpdatePublisher class. This publisher will be configured to use the PublisherGrpc.PublisherImplBase that you created earlier.

@Rule
public final GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
 
private static final String MESSAGE_ID_PREFIX = "message";
 
@Before
public void setUp() throws Exception {
String serverName = InProcessServerBuilder.generateName();
 
Server server = InProcessServerBuilder
.forName(serverName).directExecutor().addService(new MockPublisherGrpc(MESSAGE_ID_PREFIX)).build().start();
 
grpcCleanup.register(server);
...

Above we have created a gRPC server which serves in-process requests. Then we registered the mock service we created earlier.

ahead! We create publisher using that service and create an instance of class for testing.

...
 
private UpdatePublisher updatePublisher;
 
@Before
public void setUp() throws Exception {
String serverName = InProcessServerBuilder.generateName();
 
Server server = InProcessServerBuilder
.forName(serverName).directExecutor().addService(new MockPublisherGrpc(MESSAGE_ID_PREFIX)).build().start();
 
grpcCleanup.register(server);
 
ExecutorProvider executorProvider = testExecutorProvider();
ManagedChannel managedChannel = InProcessChannelBuilder.forName(serverName).directExecutor().build();
 
TransportChannel transportChannel = GrpcTransportChannel.create(managedChannel);
TransportChannelProvider transportChannelProvider = FixedTransportChannelProvider.create(transportChannel);
 
String topicName = "projects/test-project/topic/my-topic";
Publisher publisher = Publisher.newBuilder(topicName)
.setExecutorProvider(executorProvider)
.setChannelProvider(transportChannelProvider)
.build();
 
updatePublisher = new UpdatePublisher(publisher, Executors.newSingleThreadExecutor());
...

We give our publisher a channel that points to our InProcessServer. The request will be sent to the service registered by us. Finally, we can add our test.

@Test
public void testPublishOrder() throws ExecutionException, InterruptedException {
String messageId = updatePublisher.update("Some notification").get();
assertThat(messageId, containsString(MESSAGE_ID_PREFIX));
}

we’ve done! We have built our own in-process gRPC server for testing our gRPC-powered services.

You can find the code on GitHub!

Leave a Comment