The single gRPC-streaming test home: builds streaming-RPC test suites from a proto definition. Classifies each RPC by pattern (unary, server-streaming, client-streaming, bidi), then emits the required categories per pattern - ordering preservation, completion semantics (server close after stream end, client half-close), cancellation, deadline handling, partial-stream failure. Produces skeletons for Go (bufconn + Send/Recv), Python (iterators), JVM (StreamObserver), Node (call.write/end); carries the 17-code gRPC status catalog (retry semantics per AIP-194, grpc-gateway HTTP mapping) in references/status-codes.md and the wire-level / live-server streaming patterns (deadline propagation, server-side cancellation, metadata, ghz load) in references/wire-level-testing.md. Use when adding tests for a new or existing streaming RPC, auditing a suite for uncovered categories, or asserting status-code behavior. Different test surface from grpc-mock (the in-process harness itself).
68
86%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Passed
No findings from the security scan
The SKILL.md categories run against in-process harnesses (bufconn /
InProcessServer); this reference covers the live-server variant - real
channels against localhost:50051 - where deadline propagation,
cancellation observed server-side, metadata round-trips, and ghz load
behaviour are exercised over a real transport.
grpcurl for smoke, ghz for load.ghz load pass to confirm streams handle backpressure without OOM or silent drops (same reference).| Tool | Strength |
|---|---|
Language-native stubs (Go grpc.WithBlock(), Python grpc.aio, Java ManagedChannel) | Unit/integration tests |
grpcurl | Ad-hoc + smoke tests + scripts |
ghz | Load testing + benchmarks (concurrency, RPS) |
mockgrpc / mockery (Go), grpc-mock (Node) | Mock server stubs in unit tests |
Per the gRPC core concepts docs, Unary = "single request, single response." Use this to verify the RPC plumbing before testing streams:
import grpc
from orders_pb2 import OrderRequest
from orders_pb2_grpc import OrdersStub
def test_unary_create_order():
with grpc.insecure_channel("localhost:50051") as ch:
stub = OrdersStub(ch)
resp = stub.CreateOrder(OrderRequest(item_count=2), timeout=5.0)
assert resp.order_id != ""Per the gRPC core concepts docs, server-streaming = "client sends a request and gets a stream to read a sequence of messages back."
def test_server_streaming_price_ticker():
with grpc.insecure_channel("localhost:50051") as ch:
stub = PricesStub(ch)
stream = stub.SubscribePrices(SubscribeRequest(symbol="AAPL"), timeout=10.0)
ticks = []
for tick in stream:
ticks.append(tick)
if len(ticks) >= 5:
stream.cancel()
break
assert len(ticks) == 5
assert all(t.symbol == "AAPL" for t in ticks)Per the gRPC core concepts docs, client-streaming = "client writes a sequence of messages and sends them to the server."
def test_client_streaming_upload():
def chunks():
for i in range(10):
yield UploadChunk(seq=i, data=b"x" * 1024)
with grpc.insecure_channel("localhost:50051") as ch:
stub = UploadsStub(ch)
resp = stub.Upload(chunks(), timeout=10.0)
assert resp.total_chunks == 10
assert resp.total_bytes == 10 * 1024Per the gRPC core concepts docs, bidirectional streams "operate independently" - server may emit messages before reading any client message, after, or interleaved.
import asyncio
async def test_bidi_chat():
async def client_messages():
for msg in ["hello", "how are you", "bye"]:
yield ChatMessage(text=msg)
await asyncio.sleep(0.1)
async with grpc.aio.insecure_channel("localhost:50051") as ch:
stub = ChatStub(ch)
responses = []
async for resp in stub.Chat(client_messages()):
responses.append(resp)
assert len(responses) >= 3Per the gRPC core concepts docs, "Clients specify maximum wait
time; RPCs terminate with DEADLINE_EXCEEDED if exceeded."
def test_deadline_returns_correct_status():
with grpc.insecure_channel("localhost:50051") as ch:
stub = SlowStub(ch)
with pytest.raises(grpc.RpcError) as exc_info:
stub.SlowOperation(SlowRequest(), timeout=0.5)
assert exc_info.value.code() == grpc.StatusCode.DEADLINE_EXCEEDEDVerify the server-side:
def test_server_observes_deadline_propagation():
# Service should respect deadline and cancel its own downstream calls
with grpc.insecure_channel("localhost:50051") as ch:
stub = OrchestratorStub(ch)
with pytest.raises(grpc.RpcError):
stub.Compose(ComposeRequest(), timeout=0.1)
# Verify downstream call observed the cancellation
downstream_state = fetch_downstream_state()
assert downstream_state.cancelled_count >= 1Per the gRPC core concepts docs, "Either party can terminate an RPC immediately. Changes made before a cancellation are not rolled back."
def test_cancellation_is_observed_server_side():
with grpc.insecure_channel("localhost:50051") as ch:
stub = LongRunningStub(ch)
future = stub.LongOperation.future(LongRequest())
time.sleep(0.5)
future.cancel()
# Server should record cancellation
time.sleep(0.5)
state = fetch_server_metrics()
assert state.cancelled_count >= 1See status-codes-metadata-load.md
for the full status-code matrix (OK, CANCELLED, DEADLINE_EXCEEDED,
INVALID_ARGUMENT, UNAVAILABLE, ...), a request/response metadata
round-trip test, and load testing with ghz.
A prices service exposes SubscribePrices, a server-streaming RPC. QA
needs to confirm the client receives ordered ticks and that cancelling
the stream is observed server-side.
stream = stub.SubscribePrices(SubscribeRequest(symbol="AAPL"), timeout=10.0).stream.cancel() and break (Step 3).len(ticks) == 5 and every t.symbol == "AAPL".cancelled_count >= 1, proving the server observed the client cancel rather than orphaning work.Result: the ticker stream is verified for ordered delivery, a clean 10s deadline, and server-side cancellation - the three behaviors a server-streaming RPC most often regresses on.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Skip deadline + cancellation tests | Production cancellation orphans server-side work | Steps 6 + 7 |
| Test only OK and INTERNAL paths | Status-code regressions go silently | Test the matrix (status-codes reference) |
| Use BatchSpanProcessor or similar buffering on test client | Streams "complete" before all messages flush | Always synchronous in tests |
| Tests share a single channel across goroutines | Channel state contamination flakes | Per-test channel |
| Generate proto stubs at test runtime | CI flakes on plugin churn | Generate in build phase + commit |
websocket-tests (qa-realtime-protocols) - WebSocket
alternative for non-gRPC stacksserver-sent-events-tests (qa-realtime-protocols) -
one-way HTTP streaming alternative