Skip to content

gRPC Mocking

gRPC mocking enables you to create mock gRPC services for testing gRPC clients. Configure unary RPCs, streaming methods, and server reflection with protobuf definitions.

mockd’s gRPC support includes:

  • Protobuf support - Use .proto files to define your service schema
  • All RPC types - Unary, server streaming, client streaming, and bidirectional
  • Request matching - Conditional responses based on metadata and request fields
  • Server reflection - Enable tooling discovery with grpcurl and gRPC UI
  • Error simulation - Return gRPC status codes with detailed error messages
  • Template support - Dynamic responses with variables

Create a minimal gRPC mock. First, create your proto file protos/greeter.proto:

syntax = "proto3";
package helloworld;
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}

Then create your mockd configuration:

version: "1.0"
mocks:
- id: my-grpc-service
name: Greeter Service
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./protos/greeter.proto
reflection: true
services:
helloworld.Greeter:
methods:
SayHello:
response:
message: "Hello, World!"

Start the server and test:

Terminal window
# Start mockd
mockd serve --config mockd.yaml
# List services (requires grpcurl)
grpcurl -plaintext localhost:50051 list
# Call SayHello
grpcurl -plaintext -d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
# Response:
# {
# "message": "Hello, World!"
# }
mocks:
- id: grpc-endpoint
name: My gRPC Service
type: grpc
enabled: true
grpc:
# gRPC server port (required)
port: 50051
# Proto file path (required)
protoFile: ./protos/service.proto
# Multiple proto files (alternative to protoFile)
protoFiles:
- ./protos/service.proto
- ./protos/messages.proto
# Import paths for proto dependencies
importPaths:
- ./protos
- ./vendor/googleapis
# Enable gRPC server reflection (default: false)
reflection: true
# Service and method configurations
services:
package.ServiceName:
methods:
MethodName:
response: # Single response (unary/server streaming)
responses: # Multiple responses (streaming)
delay: "100ms" # Response delay
streamDelay: "50ms" # Delay between stream messages
match: # Conditional matching
metadata:
key: "value"
request:
field: "value"
error: # Return gRPC error
code: NOT_FOUND
message: "Resource not found"
FieldTypeDescription
portintgRPC server port
protoFilestringPath to a single .proto file
protoFiles[]stringPaths to multiple .proto files
importPaths[]stringAdditional proto import paths
reflectionbooleanEnable gRPC server reflection
servicesmapService and method configurations

mockd requires protobuf definitions to validate and parse messages. Proto files must be provided as file paths.

grpc:
protoFile: ./protos/service.proto

Create protos/service.proto:

syntax = "proto3";
package users;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (stream User) {}
}
message GetUserRequest {
string id = 1;
}
message ListUsersRequest {
int32 page_size = 1;
}
message User {
string id = 1;
string name = 2;
string email = 3;
}

When your service spans multiple proto files:

grpc:
protoFiles:
- ./protos/service.proto
- ./protos/messages.proto
- ./protos/common.proto

Configure import paths for proto dependencies:

grpc:
protoFile: ./protos/service.proto
importPaths:
- ./protos
- ./vendor/googleapis
- ./third_party/protobuf

This allows proto files to import other definitions:

import "google/protobuf/timestamp.proto";
import "common/types.proto";

Configure responses for each service and method using the fully qualified service name (package.ServiceName).

services:
helloworld.Greeter:
methods:
SayHello:
response:
message: "Hello!"
users.UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
services:
users.UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
created_at: "{{now}}"
delay: "100ms"
DeleteUser:
response:
success: true

Unary RPC (Single Request, Single Response)

Section titled “Unary RPC (Single Request, Single Response)”

The most common RPC type - one request, one response:

services:
UserService:
methods:
GetUser:
response:
id: "123"
name: "John Doe"
email: "john@example.com"
role: "ADMIN"

Server Streaming (Single Request, Multiple Responses)

Section titled “Server Streaming (Single Request, Multiple Responses)”

Return multiple messages in response to a single request:

services:
UserService:
methods:
ListUsers:
responses:
- id: "1"
name: "Alice"
email: "alice@example.com"
- id: "2"
name: "Bob"
email: "bob@example.com"
- id: "3"
name: "Carol"
email: "carol@example.com"
streamDelay: "100ms"

Client Streaming (Multiple Requests, Single Response)

Section titled “Client Streaming (Multiple Requests, Single Response)”

Receive multiple messages and return a single response:

services:
UserService:
methods:
BatchCreate:
response:
count: 3
success: true

Both client and server send multiple messages:

services:
ChatService:
methods:
Chat:
responses:
- type: "ack"
message: "Received"
- type: "ack"
message: "Processed"
streamDelay: "50ms"

Simulate network latency:

services:
UserService:
methods:
GetUser:
response:
id: "123"
name: "John"
delay: "500ms"
SlowOperation:
response:
status: "completed"
delay: "2s"

Control timing between streamed messages:

services:
UserService:
methods:
ListUsers:
responses:
- id: "1"
name: "Alice"
- id: "2"
name: "Bob"
- id: "3"
name: "Carol"
streamDelay: "200ms" # 200ms between each message

Use template expressions in responses:

services:
UserService:
methods:
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
created_at: "{{now}}"
timestamp: "{{timestamp}}"
GetUser:
response:
id: "user_123"
name: "Dynamic User"
fetched_at: "{{now}}"

Available templates:

TemplateDescription
{{uuid}}Random UUID
{{now}}Current ISO timestamp
{{timestamp}}Unix timestamp

Return different responses based on metadata or request field values.

Match requests based on gRPC metadata (headers):

services:
UserService:
methods:
GetUser:
match:
metadata:
authorization: "Bearer token123"
x-request-id: "req-*"
response:
id: "123"
name: "Authenticated User"

Metadata matching supports:

  • Exact match: authorization: "Bearer token123"
  • Wildcard match: x-request-id: "req-*"

Match based on message field values:

services:
UserService:
methods:
GetUser:
match:
request:
id: "123"
response:
id: "123"
name: "John Doe"
email: "john@example.com"

Match both metadata and request fields:

services:
UserService:
methods:
GetUser:
match:
metadata:
authorization: "Bearer valid-token"
request:
id: "123"
response:
id: "123"
name: "John Doe"
email: "john@example.com"

Configure multiple mocks with different match conditions for the same method:

mocks:
# Match specific user
- id: grpc-user-123
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
match:
request:
id: "123"
response:
id: "123"
name: "Admin User"
# Match not found case
- id: grpc-user-not-found
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./user.proto
services:
UserService:
methods:
GetUser:
match:
request:
id: "999"
error:
code: NOT_FOUND
message: "User with ID 999 not found"

Return gRPC status codes with detailed error information.

services:
UserService:
methods:
GetUser:
error:
code: NOT_FOUND
message: "User not found"
services:
UserService:
methods:
GetUser:
error:
code: NOT_FOUND
message: "User not found"
details:
type: "UserError"
user_id: "123"
reason: "No user exists with this ID"
CodeDescription
OKSuccess
CANCELLEDOperation cancelled
UNKNOWNUnknown error
INVALID_ARGUMENTInvalid argument provided
DEADLINE_EXCEEDEDTimeout exceeded
NOT_FOUNDResource not found
ALREADY_EXISTSResource already exists
PERMISSION_DENIEDPermission denied
RESOURCE_EXHAUSTEDResource exhausted (rate limit)
FAILED_PRECONDITIONPrecondition failed
ABORTEDOperation aborted
OUT_OF_RANGEOut of range
UNIMPLEMENTEDNot implemented
INTERNALInternal error
UNAVAILABLEService unavailable
DATA_LOSSData loss
UNAUTHENTICATEDNot authenticated

Return errors based on request conditions:

services:
UserService:
methods:
GetUser:
match:
request:
id: "forbidden"
error:
code: PERMISSION_DENIED
message: "Access to this user is forbidden"
details:
user_id: "forbidden"
required_role: "ADMIN"

Enable gRPC server reflection to allow tooling to discover services and methods at runtime.

grpc:
reflection: true

With reflection enabled, clients can:

  • Discover available services and methods
  • Get message type information
  • Use tools like grpcurl without proto files
  • Enable IDE auto-completion
Terminal window
# List all services
grpcurl -plaintext localhost:50051 list
# Output:
# grpc.reflection.v1alpha.ServerReflection
# helloworld.Greeter
# Describe a service
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
# Output:
# helloworld.Greeter is a service:
# service Greeter {
# rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
# }
# Describe a message
grpcurl -plaintext localhost:50051 describe helloworld.HelloRequest
grpc:
reflection: false

Without reflection, clients need proto files to make requests.

Create protos/users.proto:

syntax = "proto3";
package users;
service UserService {
rpc GetUser (GetUserRequest) returns (User) {}
rpc ListUsers (ListUsersRequest) returns (stream User) {}
rpc CreateUser (CreateUserRequest) returns (User) {}
rpc UpdateUser (UpdateUserRequest) returns (User) {}
rpc DeleteUser (DeleteUserRequest) returns (DeleteUserResponse) {}
}
message GetUserRequest { string id = 1; }
message ListUsersRequest { int32 page_size = 1; string page_token = 2; }
message CreateUserRequest { string name = 1; string email = 2; string role = 3; }
message UpdateUserRequest { string id = 1; string name = 2; string email = 3; }
message DeleteUserRequest { string id = 1; }
message DeleteUserResponse { bool success = 1; }
message User {
string id = 1;
string name = 2;
string email = 3;
string role = 4;
string created_at = 5;
string updated_at = 6;
}

Then configure in mockd.yaml:

version: "1.0"
mocks:
- id: user-grpc-service
name: User Service
type: grpc
enabled: true
grpc:
port: 50051
protoFile: ./protos/users.proto
reflection: true
services:
users.UserService:
methods:
GetUser:
response:
id: "user_001"
name: "John Doe"
email: "john@example.com"
role: "USER"
created_at: "2024-01-15T10:00:00Z"
delay: "50ms"
ListUsers:
responses:
- id: "user_001"
name: "Alice Smith"
email: "alice@example.com"
role: "ADMIN"
- id: "user_002"
name: "Bob Johnson"
email: "bob@example.com"
role: "USER"
streamDelay: "100ms"
CreateUser:
response:
id: "{{uuid}}"
name: "New User"
email: "new@example.com"
role: "USER"
created_at: "{{now}}"
DeleteUser:
response:
success: true

Create protos/chat.proto:

syntax = "proto3";
package chat;
service ChatService {
rpc SendMessage (ChatMessage) returns (ChatAck) {}
rpc StreamMessages (ChatRoom) returns (stream ChatMessage) {}
rpc Chat (stream ChatMessage) returns (stream ChatMessage) {}
}
message ChatRoom { string room_id = 1; }
message ChatMessage {
string id = 1;
string room_id = 2;
string sender = 3;
string text = 4;
string timestamp = 5;
}
message ChatAck { string message_id = 1; bool delivered = 2; }

Then configure:

version: "1.0"
mocks:
- id: chat-grpc-service
name: Chat Service
type: grpc
enabled: true
grpc:
port: 50052
protoFile: ./protos/chat.proto
reflection: true
services:
chat.ChatService:
methods:
SendMessage:
response:
message_id: "{{uuid}}"
delivered: true
delay: "20ms"
StreamMessages:
responses:
- id: "msg_001"
room_id: "general"
sender: "system"
text: "Welcome to the chat!"
- id: "msg_002"
room_id: "general"
sender: "alice"
text: "Hello everyone!"
streamDelay: "500ms"
Chat:
responses:
- id: "echo_001"
sender: "bot"
text: "Message received"
streamDelay: "100ms"

Create protos/orders.proto:

syntax = "proto3";
package orders;
service OrderService {
rpc GetOrder (GetOrderRequest) returns (Order) {}
rpc CreateOrder (CreateOrderRequest) returns (Order) {}
rpc CancelOrder (CancelOrderRequest) returns (CancelOrderResponse) {}
}
message GetOrderRequest { string order_id = 1; }
message CreateOrderRequest { string customer_id = 1; repeated OrderItem items = 2; }
message OrderItem { string product_id = 1; int32 quantity = 2; }
message CancelOrderRequest { string order_id = 1; string reason = 2; }
message CancelOrderResponse { bool success = 1; string message = 2; }
message Order {
string id = 1;
string customer_id = 2;
repeated OrderItem items = 3;
string status = 4;
double total = 5;
string created_at = 6;
}

Then configure with multiple match conditions:

version: "1.0"
mocks:
# Successful order lookup
- id: order-grpc-success
name: Order Service - Success
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
reflection: true
services:
orders.OrderService:
methods:
GetOrder:
match:
request:
order_id: "order_123"
response:
id: "order_123"
customer_id: "cust_001"
status: "CONFIRMED"
total: 99.99
CreateOrder:
response:
id: "{{uuid}}"
customer_id: "cust_001"
status: "PENDING"
created_at: "{{now}}"
delay: "200ms"
# Order not found
- id: order-grpc-not-found
name: Order Service - Not Found
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
services:
orders.OrderService:
methods:
GetOrder:
match:
request:
order_id: "nonexistent"
error:
code: NOT_FOUND
message: "Order not found"
# Cancel order - permission denied
- id: order-grpc-permission-denied
name: Order Service - Permission Denied
type: grpc
enabled: true
grpc:
port: 50053
protoFile: ./protos/orders.proto
services:
orders.OrderService:
methods:
CancelOrder:
match:
request:
order_id: "shipped_order"
error:
code: FAILED_PRECONDITION
message: "Cannot cancel shipped order"
details:
order_id: "shipped_order"
status: "SHIPPED"

Inspect a proto file to see available services and methods:

Terminal window
# List services from a proto file
mockd grpc list api.proto
# With import path
mockd grpc list api.proto -I ./proto

Output:

Services in api.proto:
users.UserService
- GetUser (GetUserRequest) returns (User)
- ListUsers (ListUsersRequest) returns (stream User)
- CreateUser (CreateUserRequest) returns (User)

Test gRPC endpoints directly from the CLI:

Terminal window
# Call a unary method
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}'
# With metadata
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}' \
-m "authorization:Bearer token123"
# Request body from file
mockd grpc call localhost:50051 users.UserService/CreateUser @request.json
# Plaintext mode (no TLS)
mockd grpc call localhost:50051 users.UserService/GetUser '{"id": "123"}' --plaintext
FlagDescription
-m, --metadatagRPC metadata as key:value,key2:value2
--plaintextUse plaintext (no TLS, default: true)
--prettyPretty print output (default: true)

Create a new project with gRPC configuration:

Terminal window
mockd init --template grpc-service

This generates a complete gRPC mock configuration with:

  • Greeter service with multiple RPC types
  • Unary, server streaming, client streaming, and bidirectional examples
  • Reflection enabled
  • Sample proto definition

grpcurl is a command-line tool for interacting with gRPC servers.

Terminal window
# macOS
brew install grpcurl
# Linux (download from releases)
curl -sSL https://github.com/fullstorydev/grpcurl/releases/download/v1.8.9/grpcurl_1.8.9_linux_x86_64.tar.gz | tar xz
# Go install
go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
Terminal window
# With reflection enabled
grpcurl -plaintext localhost:50051 list
# Output:
# grpc.reflection.v1alpha.ServerReflection
# helloworld.Greeter
Terminal window
grpcurl -plaintext localhost:50051 describe helloworld.Greeter
# Output:
# helloworld.Greeter is a service:
# service Greeter {
# rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
# rpc SayHelloStream ( .helloworld.HelloRequest ) returns ( stream .helloworld.HelloReply );
# }
Terminal window
grpcurl -plaintext \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
# Output:
# {
# "message": "Hello, World!"
# }
Terminal window
grpcurl -plaintext \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHelloStream
# Output (multiple messages):
# {
# "message": "Hello! (1/3)"
# }
# {
# "message": "Hello again! (2/3)"
# }
# {
# "message": "Hello one more time! (3/3)"
# }
Terminal window
grpcurl -plaintext \
-H "authorization: Bearer token123" \
-H "x-request-id: req-001" \
-d '{"id": "123"}' \
localhost:50051 users.UserService/GetUser

If reflection is disabled, provide the proto file:

Terminal window
grpcurl -plaintext \
-proto ./protos/service.proto \
-d '{"name": "World"}' \
localhost:50051 helloworld.Greeter/SayHello
Terminal window
# Save request to file
echo '{"name": "World"}' > request.json
# Call with file input
grpcurl -plaintext \
-d @ \
localhost:50051 helloworld.Greeter/SayHello < request.json

Ensure your client handles all streaming types correctly:

services:
TestService:
methods:
# Unary
UnaryMethod:
response: { status: "ok" }
# Server streaming
ServerStream:
responses:
- { seq: 1 }
- { seq: 2 }
- { seq: 3 }
streamDelay: "100ms"
# Client streaming
ClientStream:
response:
count: 5
processed: true
# Bidirectional
BidiStream:
responses:
- { echo: "received" }
streamDelay: "50ms"

Verify your client handles gRPC errors properly:

services:
UserService:
methods:
# Test not found
GetUser:
match:
request:
id: "nonexistent"
error:
code: NOT_FOUND
message: "User not found"
# Test validation error
CreateUser:
match:
request:
email: ""
error:
code: INVALID_ARGUMENT
message: "Email is required"
# Test authentication error
DeleteUser:
error:
code: UNAUTHENTICATED
message: "Authentication required"

Use delays to test client timeout behavior:

services:
SlowService:
methods:
SlowMethod:
response: { status: "completed" }
delay: "30s" # Will trigger client timeout

mockd works with any gRPC client:

  • grpcurl - Command-line testing
  • BloomRPC - GUI client for gRPC
  • Postman - API testing with gRPC support
  • gRPC UI - Web-based gRPC client
  • Your application - Native gRPC clients in any language