Skip to content

gRPC Mock

Mock external gRPC services. Unary + all streaming modes. Request matchers, auth-aware metadata matchers, dynamic port for CI safety.

Open in setup wizard

gRPC Mock — wizard-synced snippet

Gradle

testImplementation("com.trendyol:stove-grpc-mock")

Stove configuration

Stove().with {
    grpcMock {
      GrpcMockSystemOptions(port = 0)
    }
}

Test DSL

stove {
    grpcMock {
      mockUnary(
        serviceName = "com.acme.InventoryService",
        methodName = "Reserve",
        response = ReserveResponse.newBuilder().setSuccess(true).build()
      )
    }
}

In 30 seconds Register grpcMock { GrpcMockSystemOptions(port = 0) } (port 0 = dynamic). Mock with mockUnary("Svc", "Method", matcher, response). Streaming variants: mockServerStream, mockClientStream, mockBidiStream. Errors via mockError(status, message). Match per-call metadata for auth scenarios.

Configure

Stove().with {
  grpcMock {
    GrpcMockSystemOptions(
      port = 0,                              // dynamic, CI-safe
      removeStubAfterRequestMatched = false  // true = one-shot stubs
    )
  }
}.run()

Mocks

Unary

stove {
  grpcMock {
    mockUnary(
      serviceName = "com.acme.InventoryService",
      methodName = "Reserve",
      requestMatcher = RequestMatcher.ExactMessage(
        ReserveRequest.newBuilder()
          .setProductId("p-1")
          .setQuantity(1)
          .build()
      ),
      response = ReserveResponse.newBuilder()
        .setReservationId("r-1")
        .setSuccess(true)
        .build()
    )
  }
}

Custom matcher (raw bytes)

mockUnary(
  serviceName = "com.acme.AuditService",
  methodName = "Log",
  requestMatcher = RequestMatcher.Custom { bytes ->
    bytes.size > 0 && AuditEvent.parseFrom(bytes).level == "ERROR"
  },
  response = LogAck.newBuilder().setOk(true).build()
)

Server stream

mockServerStream(
  serviceName = "com.acme.NotificationService",
  methodName = "Subscribe",
  requestMatcher = RequestMatcher.ExactMessage(SubscribeRequest.newBuilder().setUserId("u1").build()),
  responses = listOf(
    Notification.newBuilder().setMessage("hi").build(),
    Notification.newBuilder().setMessage("bye").build()
  )
)

Client stream

Matching applies to the first message in the stream.

mockClientStream(
  serviceName = "com.acme.UploadService",
  methodName = "Upload",
  firstMessageMatcher = RequestMatcher.Custom { bytes ->
    UploadChunk.parseFrom(bytes).fileName == "products.csv"
  },
  response = UploadAck.newBuilder().setReceived(100).build()
)

Bidi stream

mockBidiStream(
  serviceName = "com.acme.ChatService",
  methodName = "Chat",
  handler = { requests ->
    requests.map { req ->
      ChatMessage.newBuilder().setText("echo: ${req.text}").build()
    }
  }
)

Errors

mockError(
  serviceName = "com.acme.InventoryService",
  methodName = "Reserve",
  status = Status.NOT_FOUND,
  message = "product not stocked"
)

Auth-aware matchers

metadataMatcher lets you stub differently based on headers / tokens.

mockUnary(
  serviceName = "com.acme.SecureService",
  methodName = "GetData",
  metadataMatcher = MetadataMatcher.All(
    MetadataMatcher.RequiresAuth,
    MetadataMatcher.HasHeader("x-tenant", "acme"),
    MetadataMatcher.BearerToken("user-jwt")
  ),
  requestMatcher = RequestMatcher.ExactMessage(GetDataRequest.getDefaultInstance()),
  response = DataResponse.newBuilder().setValue("ok").build()
)

Built-in matchers:

Matcher Use
BearerToken("jwt") Authorization: Bearer jwt
HasHeader("x-y", "z") exact header value
RequiresAuth any Authorization header
All(...) AND

Multiple services on one port

Several services can register on the same mock instance, no extra config:

stove {
  grpcMock {
    mockUnary("com.acme.InventoryService", "Reserve", /* ... */)
    mockUnary("com.acme.PaymentService", "Charge",   /* ... */)
  }
}

Complete example

test("order checkout flow uses inventory + payment mocks") {
  stove {
    grpcMock {
      mockUnary(
        serviceName = "com.acme.InventoryService",
        methodName = "Reserve",
        requestMatcher = RequestMatcher.ExactMessage(
          ReserveRequest.newBuilder().setProductId("p-1").build()
        ),
        response = ReserveResponse.newBuilder().setReservationId("r-1").build()
      )

      mockUnary(
        serviceName = "com.acme.PaymentService",
        methodName = "Charge",
        requestMatcher = RequestMatcher.ExactMessage(
          ChargeRequest.newBuilder().setAmount(99.99).build()
        ),
        response = ChargeResponse.newBuilder().setTransactionId("t-1").build()
      )
    }

    http {
      postAndExpectBody<OrderResponse>("/orders", orderReq.some()) {
        it.status shouldBe 201
        it.body().reservationId shouldBe "r-1"
        it.body().transactionId shouldBe "t-1"
      }
    }
  }
}

Pairs well with

  • gRPC Client for asserting against the real gRPC services
  • WireMock for HTTP-mocked upstreams in the same flow