gRPC API Testing Basics: Protobuf, Status Codes and Streaming
Inside large companies, most API calls never touch a browser: the order service talks to pricing, pricing talks to inventory, inventory talks to the warehouse, millions of times a minute. Many of these internal calls use gRPC because it’s fast and strongly typed. If you test microservices, you’ll meet it sooner or later. This lesson explains gRPC from a tester’s angle and walks through real calls to a public gRPC test server, including streaming and deadlines.
.proto file defining services, methods and message types. To test gRPC you call methods with a client (Postman, grpcurl, BloomRPC-style tools or code), check the response message fields and the gRPC status code (OK, INVALID_ARGUMENT, NOT_FOUND, PERMISSION_DENIED, UNAUTHENTICATED, DEADLINE_EXCEEDED…), test the four call types (unary, server streaming, client streaming, bidirectional), deadlines, metadata (headers) and backward compatibility of the .proto contract.Why gRPC Exists (REST vs gRPC)
| REST (JSON) | gRPC | |
|---|---|---|
| Contract | OpenAPI (optional) | .proto file (mandatory) |
| Payload | JSON text | Protocol Buffers binary (smaller, faster to parse) |
| Transport | HTTP/1.1 or HTTP/2 | HTTP/2 only |
| Style | Resources + HTTP methods | Methods (functions) on services |
| Streaming | Limited (SSE, WebSocket separately) | Built in: server, client and bidirectional |
| Errors | HTTP status codes | gRPC status codes (HTTP status is usually 200) |
| Browser support | Native | Needs gRPC-Web proxy |
| Readable with cURL? | Yes | No: binary, needs tools |
The .proto Contract
We’ll test the public grpcb.in test server (plaintext on grpcb.in:9000, TLS on grpcb.in:9001). Its hello service is defined like this:
syntax = "proto3";
package hello;
service HelloService {
rpc SayHello (HelloRequest) returns (HelloResponse); // unary
rpc LotsOfReplies (HelloRequest) returns (stream HelloResponse); // server streaming
}
message HelloRequest { string greeting = 1; }
message HelloResponse { string reply = 1; }
Key points for testers:
- Each field has a number (
= 1). The number, not the name, is what goes on the wire. Changing or reusing numbers breaks clients. - In proto3, missing fields get default values (empty string, 0, false). An API can’t tell “not sent” from “sent empty” unless it uses wrappers or
optional. Test how the service treats defaults. - Code generators create typed clients in Java, Go, Python, TypeScript and more from this file.
The Four Types of gRPC Calls
| Type | Shape | Real example |
|---|---|---|
| Unary | 1 request → 1 response | GetPrice(product_id) |
| Server streaming | 1 request → many responses | Live driver location updates for a trip |
| Client streaming | Many requests → 1 response | Upload GPS points, get a trip summary |
| Bidirectional streaming | Many ↔ many | Chat, real-time collaboration |
gRPC Status Codes (Know These for Interviews)
| Code | Name | Roughly like HTTP | Meaning |
|---|---|---|---|
| 0 | OK | 200 | Success |
| 1 | CANCELLED | 499 | Client cancelled |
| 2 | UNKNOWN | 500 | Unknown error |
| 3 | INVALID_ARGUMENT | 400 | Bad input |
| 4 | DEADLINE_EXCEEDED | 504 | Took longer than the client allowed |
| 5 | NOT_FOUND | 404 | Resource doesn’t exist |
| 6 | ALREADY_EXISTS | 409 | Duplicate |
| 7 | PERMISSION_DENIED | 403 | Not allowed |
| 8 | RESOURCE_EXHAUSTED | 429 | Quota / rate limit |
| 9 | FAILED_PRECONDITION | 400/412 | System not in the required state |
| 12 | UNIMPLEMENTED | 501 | Method not implemented |
| 13 | INTERNAL | 500 | Server bug |
| 14 | UNAVAILABLE | 503 | Service down; usually safe to retry |
| 16 | UNAUTHENTICATED | 401 | Missing or invalid credentials |
Just like 4xx vs 5xx in REST, invalid client input should give INVALID_ARGUMENT, never INTERNAL or UNKNOWN.
Calling gRPC Manually
Postman
- New → gRPC. Server URL:
grpcb.in:9000(TLS off). - Choose Using server reflection (grpcb.in supports it) or import the .proto file.
- Select
hello.HelloService / SayHello, message{"greeting": "Asha"}, click Invoke. - Response:
{"reply": "hello Asha"}with status0 OK.
grpcurl (command line)
# List services via server reflection
grpcurl -plaintext grpcb.in:9000 list
# Describe a service
grpcurl -plaintext grpcb.in:9000 describe hello.HelloService
# Unary call
grpcurl -plaintext -d '{"greeting": "Asha"}' grpcb.in:9000 hello.HelloService/SayHello
# Metadata (gRPC's headers), e.g. auth
grpcurl -plaintext -H "authorization: Bearer <token>" -d '{}' my-service:50051 orders.OrderService/ListOrders
Automating gRPC Tests with Playwright + grpc-js
Playwright’s runner, fixtures and reports work for any async test, so you can drive gRPC with the official Node client. Install it in your project:
npm install -D @grpc/grpc-js @grpc/proto-loader
Save the .proto above as hello.proto in the project root, then:
import { test, expect } from '@playwright/test';
import * as grpc from '@grpc/grpc-js';
import * as loader from '@grpc/proto-loader';
const pkg: any = grpc.loadPackageDefinition(loader.loadSync('hello.proto'));
const client = new pkg.hello.HelloService('grpcb.in:9000', grpc.credentials.createInsecure());
const deadline = (ms: number) => new Date(Date.now() + ms);
function sayHello(greeting: string, ms = 8_000): Promise<{ reply: string }> {
return new Promise((resolve, reject) =>
client.SayHello({ greeting }, { deadline: deadline(ms) }, (err: grpc.ServiceError | null, res: any) =>
err ? reject(err) : resolve(res)));
}
test('unary call returns the expected reply', async () => {
expect(await sayHello('Asha')).toEqual({ reply: 'hello Asha' });
});
test('server streaming returns several messages', async () => {
const replies: string[] = await new Promise((resolve, reject) => {
const got: string[] = [];
const stream = client.LotsOfReplies({ greeting: 'Asha' }, { deadline: deadline(8_000) });
stream.on('data', (m: any) => got.push(m.reply));
stream.on('error', reject);
stream.on('end', () => resolve(got));
});
expect(replies.length).toBeGreaterThan(1);
for (const r of replies) expect(r).toBe('hello Asha');
});
test('a too-short deadline fails with DEADLINE_EXCEEDED', async () => {
const err = await sayHello('Asha', 1).catch((e: grpc.ServiceError) => e);
expect((err as grpc.ServiceError).code).toBe(grpc.status.DEADLINE_EXCEEDED); // 4
});
When written, this returned hello Asha for the unary call, 10 messages from the stream, and status code 4 for the 1 ms deadline. Public test servers can be slow or offline, so for your own services run tests against a test environment you control.
gRPC Test Checklist
| Area | What to test |
|---|---|
| Functional | Every method with valid input; response fields and enums |
| Validation | Missing/default fields, out-of-range numbers, invalid enums → INVALID_ARGUMENT |
| Not found / conflicts | NOT_FOUND, ALREADY_EXISTS, FAILED_PRECONDITION |
| Auth | No/invalid token in metadata → UNAUTHENTICATED; wrong role → PERMISSION_DENIED |
| Deadlines | Server honours client deadlines; slow dependencies don’t hang callers |
| Streaming | Message count/order, cancellation mid-stream, errors mid-stream, back-pressure |
| Retries | UNAVAILABLE retried with back-off; non-idempotent calls not retried blindly |
| Compatibility | Old clients work after .proto changes (add fields with new numbers; never reuse numbers) |
| Performance | Latency and throughput with ghz or k6’s gRPC module |
| Security | TLS in non-local environments; reflection disabled in production if required |
Breaking vs Safe .proto Changes
| Change | Safe? |
|---|---|
| Add a new field with a new number | Safe (old clients ignore it) |
| Add a new method | Safe |
| Rename a field (same number) | Wire-safe, but breaks generated code and JSON mappings |
| Change a field’s type | Breaking |
| Change or reuse a field number | Breaking (data corruption risk) |
Remove a field without reserved | Risky: number may be reused later |
Tools like buf breaking check these rules automatically in CI, the gRPC equivalent of OpenAPI diff tools from the OpenAPI lesson.
Tools for gRPC Testing
- Postman: gRPC requests with reflection or .proto import, streaming support.
- grpcurl: cURL-like command-line client.
- Kreya, Insomnia: GUI clients.
- Code: grpc-js (TypeScript), grpcio (Python), grpc-java; run them in Playwright, pytest or JUnit.
- ghz, k6: load testing.
- buf: linting and breaking-change detection.
Interview Questions: gRPC Testing
What is gRPC?
A high-performance remote procedure call framework from Google that uses Protocol Buffers for serialization and HTTP/2 for transport, with a .proto file as the contract and generated clients in many languages.
How is gRPC different from REST?
gRPC uses a mandatory .proto contract, binary Protobuf messages, HTTP/2, method-based calls and built-in streaming, and reports errors with gRPC status codes. REST uses resources, HTTP methods, mostly JSON and HTTP status codes, and works natively in browsers.
What are the four types of gRPC calls?
Unary (one request, one response), server streaming, client streaming and bidirectional streaming.
Name some gRPC status codes.
OK (0), INVALID_ARGUMENT (3), DEADLINE_EXCEEDED (4), NOT_FOUND (5), ALREADY_EXISTS (6), PERMISSION_DENIED (7), RESOURCE_EXHAUSTED (8), UNIMPLEMENTED (12), INTERNAL (13), UNAVAILABLE (14) and UNAUTHENTICATED (16).
How do you test a gRPC API manually?
Use Postman’s gRPC request or grpcurl with server reflection or the .proto file, invoke each method with valid and invalid messages, pass auth via metadata and check response fields and status codes.
What is a deadline in gRPC?
The maximum time the client is willing to wait for a call. When it expires, the call fails with DEADLINE_EXCEEDED and the server should stop work. Testers verify services set and honour deadlines.
What changes to a .proto file are breaking?
Changing a field’s type or number, reusing a deleted field’s number, removing fields or methods clients use, and changing a field from singular to repeated. Adding new fields with new numbers and new methods is safe.
What is gRPC metadata?
Key-value pairs sent with a call, similar to HTTP headers, used for authentication tokens, tracing ids and other context.
What is server reflection?
A gRPC service that lets clients list services and method definitions at runtime without the .proto file. It’s useful for testing tools and is often disabled in production.
Why can’t you test gRPC with plain cURL?
gRPC messages are binary Protobuf frames over HTTP/2 with specific framing, so you need a gRPC-aware client such as grpcurl, Postman or generated code.