Workflow-driven skill that builds gRPC 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). Use when adding tests for a new streaming RPC or auditing a suite for uncovered categories. Different test surface from grpc-interceptor-test-author (interceptor layer) and grpc-mock (harness); for wire-level streaming semantics use grpc-streaming-tests, not this.
75
94%
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
Streaming RPCs are where gRPC clients and servers most often diverge from the proto contract - message ordering, completion signalling, cancellation, and partial-failure semantics are all testable surfaces. gRPC defines four streaming patterns - unary, server-streaming, client-streaming, bidirectional - per grpc.io/docs/what-is-grpc/core-concepts/; Step 1 classifies each RPC by pattern.
This skill walks through producing a comprehensive test suite
from the proto file. It composes
grpc-mock for the test harness and
grpc-status-code-mapping-reference
for status-code assertions.
protobuf-versioning-strategy-reference
rules on stream changes are subtle).Walk the .proto file and tabulate:
service Chat {
rpc Send(Message) returns (Ack); // unary
rpc Subscribe(SubReq) returns (stream Event); // server streaming
rpc Upload(stream Chunk) returns (UploadResult); // client streaming
rpc Conversation(stream Message) returns (stream Reply); // bidi
}| RPC | Pattern | Required test categories |
|---|---|---|
Send | unary | success, every status code per grpc-status-code-mapping-reference |
Subscribe | server-stream | success, ordering, server-side close after N messages, server-side mid-stream error, client-side cancel mid-stream, deadline-exceeded mid-stream |
Upload | client-stream | success, server completes before client finishes, server-side error mid-upload, client-side cancel before send, empty stream |
Conversation | bidi | success, ordering per direction, client closes send while still receiving, server closes send while still receiving, both close, deadline mid-conversation, error mid-conversation |
def test_send_returns_ok(stub):
resp = stub.Send(Message(body="hi"))
assert resp.ok
def test_send_returns_invalid_argument(stub):
with pytest.raises(grpc.RpcError) as exc:
stub.Send(Message(body=""))
assert exc.value.code() == grpc.StatusCode.INVALID_ARGUMENTOrdering preservation - per gRPC docs: "gRPC guarantees message ordering within an individual RPC call."
def test_subscribe_preserves_ordering(stub_with_fake):
stub_with_fake.fake_response_stream = [
Event(seq=1), Event(seq=2), Event(seq=3),
]
received = list(stub_with_fake.Subscribe(SubReq()))
assert [e.seq for e in received] == [1, 2, 3]Server closes after N messages - completion is the finalisation signal:
def test_subscribe_completes_after_finite_stream(stub_with_fake):
stub_with_fake.fake_response_stream = [Event(seq=1), Event(seq=2)]
events = list(stub_with_fake.Subscribe(SubReq()))
# If list(...) terminates, the server-side close was signalled.
assert len(events) == 2Server-side error mid-stream:
def test_subscribe_propagates_error_mid_stream(stub_with_fake):
stub_with_fake.fake_response_stream = [Event(seq=1)]
stub_with_fake.fake_post_yield_status = grpc.StatusCode.INTERNAL
events_iter = stub_with_fake.Subscribe(SubReq())
# Receive first event OK
next(events_iter)
# Subsequent receive fails with INTERNAL per grpc-status-code-mapping-reference
with pytest.raises(grpc.RpcError) as exc:
next(events_iter)
assert exc.value.code() == grpc.StatusCode.INTERNALClient cancel mid-stream:
def test_client_cancel_mid_stream(stub_with_fake):
stub_with_fake.fake_response_stream = [Event(seq=i) for i in range(100)]
events_iter = stub_with_fake.Subscribe(SubReq())
next(events_iter) # receive one
events_iter.cancel()
with pytest.raises(grpc.RpcError) as exc:
next(events_iter)
assert exc.value.code() == grpc.StatusCode.CANCELLEDDeadline-exceeded mid-stream:
def test_subscribe_deadline_exceeded(stub_with_slow_fake):
events_iter = stub_with_slow_fake.Subscribe(SubReq(), timeout=0.1)
with pytest.raises(grpc.RpcError) as exc:
for _ in events_iter:
pass
assert exc.value.code() == grpc.StatusCode.DEADLINE_EXCEEDEDCategories: success, server completes before client finishes (further
Send yields io.EOF), server-side error mid-upload, client-side
cancel before send, empty stream. Go bufconn skeletons:
references/test-skeletons.md.
The two streams operate independently, so test each direction plus a
concurrent send/recv case. Categories: ordering per direction, client
closes send while still receiving (CloseSend), server closes send
while still receiving, both close, deadline mid-conversation, error
mid-conversation. Go skeletons:
references/test-skeletons.md.
For each streaming RPC, generate the matrix:
Send Subscribe Upload Conversation
---- --------- ------ ------------
success X X X X
ordering preserved - X X X (both)
N msgs then close - X X X
mid-stream server error - X X X
deadline exceeded X X X X
client cancel X X X X
server cancel - X X X
empty stream - X X X
client half-close - - - X
server half-close - - - XEmpty cells in covered patterns = coverage gap. PR must justify or add a test.
Per grpc-mock:
| Language | Harness |
|---|---|
| Go | bufconn in-process server + t.Cleanup |
| Python | pytest fixture with [::]:0 port + iterator-based stream API |
| JVM | InProcessServerBuilder + StreamObserver |
| Node | @grpc/grpc-js server with bindAsync("127.0.0.1:0") + call.write / call.end |
Don't use mockgen / interface-mocks for streaming - they skip
the marshalling + ordering guarantees that the streaming
contract depends on.
tests/grpc-streaming/
__init__.py
conftest.py # shared fixtures (in-process server)
test_subscribe.py # server-streaming
test_upload.py # client-streaming
test_conversation.py # bidi
test_send.py # unary (sibling)
README.md # the coverage matrixThe README.md should document the matrix from Step 3 so
reviewers and new contributors can see at a glance what is and
isn't covered.
Per
protobuf-versioning-strategy-reference,
changing an RPC's streaming pattern is always breaking:
service Chat {
- rpc Subscribe(SubReq) returns (Event);
+ rpc Subscribe(SubReq) returns (stream Event);
}The wire format differs, so old clients won't parse the new
response. Verify breaking-change detection via
buf-cli-lint-breaking-build
catches this with FILE or PACKAGE category.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Single happy-path test per streaming RPC | Misses ordering / cancellation / deadline categories | Use Step 2's per-pattern matrix |
| Test asserts on the last message only | Misses ordering bugs in earlier messages | Collect entire stream, assert on the full sequence |
time.sleep to wait for server messages | Race-prone; flakes in CI | Use channel close / iterator exhaustion as completion signal |
| Cancellation test sleeps then cancels | Race: server may finish before cancel | Inject a controllable blocker in the fake server |
| No deadline-exceeded test | Production deadlines surface in mid-stream | Always include - per grpc-status-code-mapping-reference, DEADLINE_EXCEEDED is its own code |
| Mock at interface level for streaming | Skips marshalling + ordering | In-process server only for streaming |
| Bidi test where client and server are deterministic-interleaved | Misses race conditions inherent in "operate independently" | One test per direction + one test with concurrent send/recv |
Don't CloseSend() in client-streaming tests | Server waits forever | Always close the send side explicitly |
Send blocks; Java StreamObserver is
push-based; Python is iterator-pull. Tests are stack-specific.protobuf-compat-checking (in the qa-contract-testing plugin).ghz-load (unary load) or hand-rolled
multi-client streaming harnesses.grpc-status-code-mapping-reference.grpc-mock.protobuf-versioning-strategy-reference.buf-cli-lint-breaking-build.grpc-streaming-tests.