Mocking gRPC Services in Java: GrpcMock & WireMock
Mocking gRPC services in Java is more involved than mocking REST APIs. You can't just stub HTTP responses — you need to mock at the gRPC protocol level, dealing with Protobuf messages, service stubs, and channel configurations. Two libraries make this tractable: GrpcMock for in-process gRPC mocking and WireMock for HTTP-based gRPC-JSON transcoding.
Why Mock gRPC Dependencies?
In a microservices architecture, your service likely calls other gRPC services. For unit and integration tests, you want to:
- Test your service in isolation without starting all dependencies
- Control the responses from downstream services
- Test error scenarios (timeouts, NOT_FOUND, UNAVAILABLE) that are hard to reproduce with real services
- Keep tests fast and deterministic
Mocking is the answer. The question is which approach fits your test level.
GrpcMock: In-Process gRPC Mocking
GrpcMock runs a real gRPC server in-process, configured to respond with your test data. It's the most faithful mock — your code talks to a real gRPC channel, just one backed by a controlled fake.
Add dependency (Maven):
<dependency>
<groupId>io.github.jstoyanov</groupId>
<artifactId>grpcmock-core</artifactId>
<version>0.12.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.jstoyanov</groupId>
<artifactId>grpcmock-junit5</artifactId>
<version>0.12.0</version>
<scope>test</scope>
</dependency>Gradle:
testImplementation 'io.github.jstoyanov:grpcmock-core:0.12.0'
testImplementation 'io.github.jstoyanov:grpcmock-junit5:0.12.0'Basic Unary Method Mock
import io.github.jstoyanov.grpcmock.GrpcMock;
import io.github.jstoyanov.grpcmock.junit5.GrpcMockExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static io.github.jstoyanov.grpcmock.GrpcMock.*;
@ExtendWith(GrpcMockExtension.class)
class OrderServiceTest {
@Test
void getOrder_returnsOrderDetails() {
// Set up the mock response
stubFor(unaryMethod(OrderServiceGrpc.getGetOrderMethod())
.withRequest(GetOrderRequest.newBuilder()
.setOrderId("order-123")
.build())
.willReturn(GetOrderResponse.newBuilder()
.setOrderId("order-123")
.setStatus("SHIPPED")
.setTotalAmount(9999)
.build()));
// Create client using the mock server's port
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", GrpcMock.getGlobalPort())
.usePlaintext()
.build();
OrderServiceGrpc.OrderServiceBlockingStub stub =
OrderServiceGrpc.newBlockingStub(channel);
// Call and assert
GetOrderResponse response = stub.getOrder(
GetOrderRequest.newBuilder().setOrderId("order-123").build()
);
assertThat(response.getStatus()).isEqualTo("SHIPPED");
assertThat(response.getTotalAmount()).isEqualTo(9999);
channel.shutdown();
}
}Mocking Error Responses
@Test
void getOrder_notFound_throwsStatusRuntimeException() {
stubFor(unaryMethod(OrderServiceGrpc.getGetOrderMethod())
.willReturn(statusException(Status.NOT_FOUND
.withDescription("Order not found"))));
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", GrpcMock.getGlobalPort())
.usePlaintext()
.build();
OrderServiceGrpc.OrderServiceBlockingStub stub =
OrderServiceGrpc.newBlockingStub(channel);
StatusRuntimeException exception = assertThrows(
StatusRuntimeException.class,
() -> stub.getOrder(
GetOrderRequest.newBuilder().setOrderId("missing").build()
)
);
assertThat(exception.getStatus().getCode())
.isEqualTo(Status.Code.NOT_FOUND);
assertThat(exception.getStatus().getDescription())
.isEqualTo("Order not found");
channel.shutdown();
}
@Test
void getOrder_unavailable_triggersRetry() {
// First call fails, second succeeds
stubFor(unaryMethod(OrderServiceGrpc.getGetOrderMethod())
.withRequestMessage(matching(GetOrderRequest.newBuilder()
.setOrderId("flaky-order")
.build()))
.willReturn(statusException(Status.UNAVAILABLE))
.nextWillReturn(response(GetOrderResponse.newBuilder()
.setOrderId("flaky-order")
.setStatus("DELIVERED")
.build())));
// Your service should retry on UNAVAILABLE...
}Server Streaming Mock
@Test
void listOrders_returnsAllOrders() {
List<GetOrderResponse> orders = List.of(
GetOrderResponse.newBuilder().setOrderId("1").setStatus("PENDING").build(),
GetOrderResponse.newBuilder().setOrderId("2").setStatus("SHIPPED").build(),
GetOrderResponse.newBuilder().setOrderId("3").setStatus("DELIVERED").build()
);
stubFor(serverStreamingMethod(OrderServiceGrpc.getListOrdersMethod())
.willReturn(orders));
ManagedChannel channel = ManagedChannelBuilder
.forAddress("localhost", GrpcMock.getGlobalPort())
.usePlaintext()
.build();
OrderServiceGrpc.OrderServiceBlockingStub stub =
OrderServiceGrpc.newBlockingStub(channel);
List<GetOrderResponse> result = new ArrayList<>();
stub.listOrders(ListOrdersRequest.newBuilder().build())
.forEachRemaining(result::add);
assertThat(result).hasSize(3);
assertThat(result.get(0).getOrderId()).isEqualTo("1");
channel.shutdown();
}Verifying Calls
@Test
void placeOrder_callsInventoryService() {
// Stub the downstream dependency
stubFor(unaryMethod(InventoryServiceGrpc.getCheckStockMethod())
.willReturn(CheckStockResponse.newBuilder()
.setAvailable(true)
.setQuantity(100)
.build()));
// Execute
orderService.placeOrder(OrderRequest.newBuilder()
.setProductId("prod-456")
.setQuantity(2)
.build());
// Verify the downstream service was called
verifyThat(calledMethod(InventoryServiceGrpc.getCheckStockMethod())
.withRequest(CheckStockRequest.newBuilder()
.setProductId("prod-456")
.build()));
verifyThat(1, calledMethod(InventoryServiceGrpc.getCheckStockMethod()));
}Mockito for Service Layer Unit Tests
For pure unit tests of your service logic, use Mockito to mock the gRPC stub directly:
@ExtendWith(MockitoExtension.class)
class OrderProcessorTest {
@Mock
private InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;
@Mock
private PaymentServiceGrpc.PaymentServiceBlockingStub paymentStub;
@InjectMocks
private OrderProcessor orderProcessor;
@Test
void processOrder_insufficientStock_throwsException() {
// Arrange
when(inventoryStub.checkStock(any(CheckStockRequest.class)))
.thenReturn(CheckStockResponse.newBuilder()
.setAvailable(false)
.setQuantity(0)
.build());
OrderRequest request = OrderRequest.newBuilder()
.setProductId("out-of-stock-item")
.setQuantity(1)
.build();
// Act & Assert
assertThrows(
InsufficientStockException.class,
() -> orderProcessor.processOrder(request)
);
verify(inventoryStub).checkStock(any());
verify(paymentStub, never()).chargeCard(any());
}
@Test
void processOrder_paymentFails_rollsBackInventory() {
when(inventoryStub.checkStock(any()))
.thenReturn(CheckStockResponse.newBuilder()
.setAvailable(true)
.setQuantity(10)
.build());
when(inventoryStub.reserveStock(any()))
.thenReturn(ReserveStockResponse.newBuilder()
.setReservationId("res-789")
.build());
when(paymentStub.chargeCard(any()))
.thenThrow(new StatusRuntimeException(Status.FAILED_PRECONDITION
.withDescription("Card declined")));
OrderRequest request = OrderRequest.newBuilder()
.setProductId("product-123")
.setQuantity(1)
.build();
assertThrows(PaymentFailedException.class,
() -> orderProcessor.processOrder(request));
// Verify inventory reservation was released
verify(inventoryStub).releaseReservation(
ReleaseReservationRequest.newBuilder()
.setReservationId("res-789")
.build()
);
}
}WireMock for gRPC-JSON Transcoding
If your gRPC service uses gRPC-HTTP JSON transcoding (common with Envoy or Google Cloud Endpoints), you can test it via WireMock mocking the HTTP/JSON interface:
@WireMockTest
class OrderServiceHttpTest {
@Test
void getOrder_viaTr anscoding(WireMockRuntimeInfo wireMock) {
// Stub the HTTP endpoint that maps to GetOrder RPC
wireMock.getWireMock().register(
WireMock.get(urlPathEqualTo("/v1/orders/order-123"))
.willReturn(aResponse()
.withStatus(200)
.withHeader("Content-Type", "application/json")
.withBody("""
{
"orderId": "order-123",
"status": "SHIPPED",
"totalAmount": 9999
}
"""))
);
// Use HTTP client (not gRPC stub) to test the transcoded API
RestTemplate restTemplate = new RestTemplate();
OrderResponse response = restTemplate.getForObject(
wireMock.getHttpBaseUrl() + "/v1/orders/order-123",
OrderResponse.class
);
assertThat(response.getOrderId()).isEqualTo("order-123");
assertThat(response.getStatus()).isEqualTo("SHIPPED");
}
}Testing Interceptors
gRPC interceptors (auth, logging, retry) need dedicated tests:
class AuthInterceptorTest {
private Server server;
private ManagedChannel channel;
private OrderServiceGrpc.OrderServiceBlockingStub stub;
@BeforeEach
void setUp() throws IOException {
server = ServerBuilder.forPort(0)
.addService(ServerInterceptors.intercept(
new OrderServiceImpl(),
new AuthInterceptor()
))
.build()
.start();
channel = ManagedChannelBuilder
.forAddress("localhost", server.getPort())
.usePlaintext()
.build();
stub = OrderServiceGrpc.newBlockingStub(channel);
}
@Test
void requestWithValidToken_passes() {
Metadata headers = new Metadata();
headers.put(
Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER),
"Bearer valid-jwt-token"
);
OrderServiceGrpc.OrderServiceBlockingStub authStub =
MetadataUtils.attachHeaders(stub, headers);
GetOrderResponse response = authStub.getOrder(
GetOrderRequest.newBuilder().setOrderId("123").build()
);
assertNotNull(response);
}
@Test
void requestWithoutToken_returnsUnauthenticated() {
StatusRuntimeException ex = assertThrows(
StatusRuntimeException.class,
() -> stub.getOrder(
GetOrderRequest.newBuilder().setOrderId("123").build()
)
);
assertThat(ex.getStatus().getCode())
.isEqualTo(Status.Code.UNAUTHENTICATED);
}
}Test Configuration with Spring Boot
For Spring Boot applications using gRPC:
@SpringBootTest
@ExtendWith(GrpcMockExtension.class)
class OrderServiceSpringTest {
@GrpcClient("inventory-service")
private InventoryServiceGrpc.InventoryServiceBlockingStub inventoryStub;
@Autowired
private OrderService orderService;
@BeforeEach
void configureMock() {
stubFor(unaryMethod(InventoryServiceGrpc.getCheckStockMethod())
.willReturn(CheckStockResponse.newBuilder()
.setAvailable(true)
.setQuantity(50)
.build()));
}
@Test
void placeOrder_withAvailableStock_succeeds() {
PlaceOrderResult result = orderService.placeOrder(
"product-123", 1, "customer-456"
);
assertThat(result.isSuccess()).isTrue();
assertThat(result.getOrderId()).isNotEmpty();
}
}Monitoring gRPC Services in Production
Mocking handles test environments. For production monitoring of gRPC services, use health checks. Most gRPC services implement the gRPC health checking protocol.
HelpMeTest monitors HTTP health endpoints that most gRPC deployments expose alongside the gRPC port:
curl -fsSL https://helpmetest.com/install | bash
helpmetest health <span class="hljs-string">"order-service-grpc" <span class="hljs-string">"2m"Run this in your deployment pipeline — if the service goes silent, your team gets an alert within 2 minutes.
Summary
| Approach | Use Case | Complexity |
|---|---|---|
| GrpcMock | Integration tests against real gRPC channel | Medium |
| Mockito | Unit tests of service logic | Low |
| WireMock | Testing gRPC-JSON transcoding layer | Low-Medium |
| In-process server | Full integration with real generated code | Medium |
Start with Mockito for unit tests of business logic, then add GrpcMock integration tests for the gRPC layer. Reserve WireMock for services behind HTTP transcoding proxies.