May 13, 2026
How to Mock a gRPC Service in CI
Mocking HTTP in CI is a solved problem. Mocking gRPC isn't, because most tooling assumed everyone would just bring up the real service. We kept hitting this ourselves — every other early MockForge user asked for the gRPC equivalent of mockforge serve --spec openapi.json, and "we're working on it" was the answer for longer than we wanted. This is a walkthrough of the actual options if you're there now, and how the one we ended up shipping works.
The problem
Your service depends on a gRPC API — an internal one, a vendor's, or a Google/AWS managed service. Your CI needs to run tests that call it. You have three options, and only one of them is good:
- Run the real service in CI. Doable for internal services, painful for vendor ones, impossible for SaaS. Adds build time and turns flaky-upstream incidents into your CI's problem.
- Hand-roll a fake. Generate Go/Rust/Python stubs from the proto, implement the methods you care about, run it in a sidecar. Works, but you maintain it forever.
- Use a generic gRPC mock server. What this post is about.
Step 1: Have the proto file
A gRPC mock needs the same proto your real service uses. If you're calling an internal service, grab the .proto from its repo. For vendor APIs, the proto is usually published — Google's are in googleapis/googleapis, AWS in their respective SDK repos.
Save it somewhere your CI can read it. For this post, assume proto/orders.proto with a service like:
service OrderService {
rpc GetOrder(GetOrderRequest) returns (Order);
rpc ListOrders(ListOrdersRequest) returns (stream Order);
}
Step 2: Start the mock server
With MockForge's CLI installed (cargo install mockforge-cli), one command brings up a gRPC server that implements every method in the proto. Each method returns realistic dummy data inferred from the message schema:
$ mockforge serve --proto proto/orders.proto --grpc-port 50051
Listening on 0.0.0.0:50051 (gRPC reflection enabled)
Reflection is on by default, so you can probe with grpcurl localhost:50051 list before you wire it into your client.
Step 3: Point your tests at it
In your test setup, override the gRPC channel to dial localhost:50051 instead of the real upstream. In Go:
conn, err := grpc.Dial(
os.Getenv("ORDER_SERVICE_URL"), // "localhost:50051" in CI
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
Use a fixed ORDER_SERVICE_URL env var in your CI workflow, and the tests run against the mock without code changes.
Step 4: Customize specific responses
Default behavior is "return any valid response for the proto." For tests that need specific data, drop YAML overrides next to the proto:
# mockforge.yaml
grpc:
overrides:
- service: OrderService
method: GetOrder
match:
order_id: "missing-001"
response:
status: NOT_FOUND
message: "order not found"
- service: OrderService
method: GetOrder
response:
body:
id: "order-001"
total_cents: 4299
currency: USD
Now GetOrder(order_id="missing-001") returns NOT_FOUND, everything else returns the static synthetic order. Rules are evaluated in declaration order; the first one that matches wins. Catch-all rules (no match block) always fire for the named method. Per-method response templating with request-field substitution is on the roadmap; for now bodies are static JSON, which is enough for most CI flows.
Step 5: Wire it into your CI workflow
For GitHub Actions, that's a single service block:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- run: cargo install mockforge-cli
- run: |
mockforge serve --proto proto/orders.proto --grpc-port 50051 &
sleep 2
- run: go test ./...
env:
ORDER_SERVICE_URL: localhost:50051
If you'd rather not install in CI, MockForge Cloud will host the same mock at <slug>.mocks.mockforge.dev — point your tests at the URL and skip the install step entirely.
When to reach for something else
Two cases where a generic gRPC mock isn't the right tool. First, contract tests — if you need to assert that the real service's responses match its spec, you want Pact or similar, not a mock. Second, performance benchmarks — mocks return synthetic data instantly, which gives you misleading p99 numbers. Use the real service or a recorded replay.
For everything else — feature work that depends on an unfinished service, CI tests that shouldn't hit production, isolated reproducer environments — a proto-driven mock is the cheapest path.