Skip to content

gRPC Client

Call your app's gRPC services from tests. Supports Wire, grpc-kotlin, and custom client factories. Unary, server-stream, client-stream, bidi.

Open in setup wizard

gRPC client — wizard-synced snippet

Gradle

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

Stove configuration

Stove().with {
    grpc {
      GrpcSystemOptions(host = "localhost", port = 9090)
    }
}

Test DSL

stove {
    grpc {
      wireClient<MyServiceClient>().getItem(GetItemRequest(id = "1")).status shouldBe "OK"
    }
}

In 30 seconds Register grpc { GrpcSystemOptions(host = "localhost", port = 9090) }. Use wireClient<T>() (Wire) or channel<T>() (gRPC-kotlin). For raw access, rawChannel { channel -> }. Streaming via coroutines: serverStream, clientStream, bidiStream.

Configure

Stove().with {
  grpc {
    GrpcSystemOptions(
      host = "localhost",
      port = 9090,
      usePlaintext = true,
      timeout = 30.seconds,
      interceptors = listOf(/* ClientInterceptor */),
      metadata = Metadata().apply {
        put(Metadata.Key.of("x-source", Metadata.ASCII_STRING_MARSHALLER), "stove")
      }
    )
  }
}.run()
Field Use
host, port Where your gRPC server listens
usePlaintext true for local tests; flip when testing TLS
timeout Per-call deadline
interceptors ClientInterceptor chain
metadata Default per-call metadata (auth, tracing, ...)
createChannel Custom ManagedChannel factory
createWireClient Custom Wire client factory

DSL

Wire client

stove {
  grpc {
    wireClient<OrderServiceClient>().createOrder(
      OrderRequest(userId = "u1", amount = 99.99)
    ).status shouldBe OrderStatus.CREATED
  }
}

grpc-kotlin channel

stove {
  grpc {
    val stub = channel<OrderServiceGrpcKt.OrderServiceCoroutineStub>()
    stub.createOrder(orderRequest).status shouldBe "CREATED"
  }
}

Per-call metadata override

stove {
  grpc {
    withEndpoint(::OrderServiceClient) {
      metadata.put(Metadata.Key.of("authorization", Metadata.ASCII_STRING_MARSHALLER), "Bearer x")
      createOrder(orderRequest)
    }
  }
}

Raw channel access

stove {
  grpc {
    rawChannel { channel ->
      val stub = MyServiceGrpc.newBlockingStub(channel)
      stub.something()
    }
  }
}

Streaming

stove {
  grpc {
    // server stream
    serverStream(::OrderServiceClient) {
      val flow = streamOrders(StreamRequest(userId = "u1"))
      flow.toList() shouldHaveSize 10
    }

    // client stream
    clientStream(::OrderServiceClient) {
      val reply = bulkCreate(flowOf(o1, o2, o3))
      reply.created shouldBe 3
    }

    // bidi
    bidiStream(::OrderServiceClient) {
      val out = chat(flowOf("hi", "hello"))
      out.toList().size shouldBe 2
    }
  }
}

Error handling

stove {
  grpc {
    shouldThrow<StatusException> {
      wireClient<OrderServiceClient>().getOrder(OrderRequest(id = "missing"))
    }.status.code shouldBe Status.Code.NOT_FOUND
  }
}

Multiple gRPC services (keyed)

object Inventory : SystemKey
object Payments  : SystemKey

Stove().with {
  grpc(Inventory) { GrpcSystemOptions(host = "localhost", port = 9090) }
  grpc(Payments)  { GrpcSystemOptions(host = "localhost", port = 9091) }
}

stove {
  grpc(Inventory) { wireClient<InventoryClient>().reserve(/* ... */) }
  grpc(Payments)  { wireClient<PaymentClient>().charge(/* ... */) }
}

See Multiple Systems.

Pairs well with

  • gRPC Mock for mocking upstream gRPC services
  • Tracing for full call chain with gRPC spans
  • Recipes for multi-system flows