Skip to content

blackbull.testing.grpc

blackbull.testing.grpc

An app-facing seam for testing your own gRPC servicers.

BlackBull ships a gRPC server and no gRPC client, and this module does not add one. What it adds is the boilerplate the framework's own gRPC tests repeat — serve the app on an ephemeral h2c port, POST with content-type: application/grpc, read the status back out of the trailing headers — behind one helper::

async with GrpcTestServer(app) as grpc:
    reply = await grpc.unary('/demo.Greeter/SayHello', b'world')

assert reply.status is GrpcStatus.OK
assert reply.message == b'hi world'

Why a seam is needed at all. Every gRPC response reports its status in trailing headers — success and error alike — so a transport with no http.response.trailers support never observes completion. That is why the framework's own gRPC tests moved off the in-process TestClient and onto :class:~blackbull.client.http2.HTTP2Client over a real socket: HTTP2Client handles trailers natively and folds them into res.headers. An application developer testing a servicer needs the same thing, and until now had to rediscover it.

The gRPC analogue of :class:~blackbull.testing.native.NativeTestServer, and deliberately the same shape: a real server on a loopback port, the whole dispatch path exercised, and the port left public so anything else can drive it too.

GrpcReply dataclass

One gRPC response, with the trailer fields already read out.

status and grpc_message come from trailing headers, which is the whole reason this seam exists — reading them off the response object is what an app developer would otherwise have to work out.

GrpcTestServer

Serve app on an ephemeral h2c port and call its gRPC methods.

async with only: the server shares the test's event loop, as it shares the process loop in production.

unary(method, request=b'', *, metadata=None, timeout=5.0) async

Call method with one request message and read the reply.

The length-prefixed framing is applied for you: pass the message bytes your servicer expects to receive, not an encoded frame.