Testing gRPC client with mock server and Testcontainers

Sulyz Andrey
skyro-tech
Published in
3 min readOct 2, 2023

Nowadays I notice that more and more teams are building their cross-service communication on API contracts. Some prefer to work with REST and choose OpenAPI contracts, while others work through gRPC and use Protobuf.

Working with OpenAPI has its own interesting moments, but basically, it is still REST, which remains familiar and understandable to many developers. When it comes to testing it, we always can use MockServer, which has good support and several usefull Java libraries.

But, if you choose gRPC, you need to find another tool for testing your code. I hope that supportion of gRPC will be added to MockServer. This feature is in the plans, but at the current moment, we don’t have any news of it. So, we should be looking for another solution like GripMock.

First of all, let’s look at how GripMock works. I consider that the documentation of this tool is enough to get started with it. So, we just look at basic steps.

  1. Preparing of running: we need to have a common folder for our proto files. The folder must mount as a volume to /proto folder in the container.
  2. Running of a container: we need to send command to run of the container which contains names of proto files. Notice that names of files must start from /proto/. Also if you want to use Google types like Empty, you need to add the flag in the command — imports=/protobuf.
  3. Adding of stubs: all stubs for GripMock are only json files which contain input and output. You can find several examples here. To add stub to the container, you need to use any http client and make a request to your container with port 4771 and POST method to /add.

Okay, now we can look at Testcontainers’ side. I think that if you read this article, you know what it is and how to use it. But instead of adding Kafka or Postgres container, we need to create a GenericContainer:

public class GripMockContainer extends GenericContainer<GripMockContainer> {

private static final DockerImageName GRIP_MOCK = DockerImageName
.parse("tkpd/gripmock")
.withTag("latest");

public GripMockContainer() {
super(GRIP_MOCK);
}

public static final GripMockContainer instance = new GripMockContainer()
.withNetworkAliases("gripmock")
.withExposedPorts(4770, 4771)
.withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger(GripMockContainer.class)))
.waitingFor(
new LogMessageWaitStrategy()
.withRegEx(".*Serving gRPC on tcp://:4770.*")
.withTimes(1)
.withStartupTimeout(Duration.ofSeconds(30))
);

public final int getApiPort() {
return getMappedPort(4771);
}

public final int getGrpcPort() {
return getMappedPort(4770);
}

}

Now GripMockContainer can be used as usual container. We just need to run it with the correct command:

public class JupiterTestExecutionListener implements TestExecutionListener {

public void testPlanExecutionStarted(TestPlan testPlan) {
List<String> mockProtoFiles = getProtoFilesFromTempFolder(protoTempFolder);

Containers.gripMock
.withFileSystemBind(protoTempFolder.toString(), "/proto")
.withCommand(mockProtoFiles.toArray(String[]::new));

// Run containers

TestExecutionListener.super.testPlanExecutionStarted(testPlan);
}

}

As you know, we need to create folder with proto files and mount it into the container. I do it in the implementation of TestExecutionListener of Junit 5 before starting of tests. getProtoFilesFromTempFolder() method creates temp folder, copies proto files from different modules to there and return names of files.

I run contrainers manually because we make a container with our application too and run it near by others. I wrote about this approach in the article. But you can use other approaches. Example, I like the approach from new version of Spring Boot.

Anyway, after running of the GripMock container, you need to add stubs there. You can do it like this:

public static void addGrpcStub(Path stubPath, String host, int port) {
HttpClient client = HttpClient.newHttpClient();

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("http://" + host + ":" + port + "/api"))
.header("Content-Type", "application/json")
.POST(BodyPublishers.ofString(parseJson(stubPath)))
.build();

String result = client.send(request, BodyHandlers.ofString()).body();

System.out.println("Result of adding gRPC stub: " + result);
}

After it you can easy create new blocking stub and test your application!

In the end, I want to share a useful GripMock fork that I myself use in my tests. GripMock develops quite slowly for a new project. The frustrating thing for me was that GripMock does not have the ability to return gRPC errors other than UNAVAILABLE. But later I found the interestion fork which has the better documentation and supportion than original GripMock.

I note that the fork has a slightly different API for adding of stubs.

--

--