Mocking gRPC Services in Java: GrpcMock & WireMock

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.

Read more