← Back to Engineering Notes

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:

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.