MockServer supports mocking gRPC services by transparently converting gRPC requests (protobuf over HTTP/2) into JSON-over-HTTP requests internally. This allows the standard expectation matching engine to handle gRPC requests using the same JSON format used for HTTP mocking.
When MockServer receives a gRPC request:
This means you can set up gRPC expectations using the same JSON format and client APIs as HTTP mocking — no special gRPC client tooling is needed.
MockServer needs proto descriptors to convert between protobuf binary and JSON. There are three ways to load them:
Compile your .proto files to descriptor sets and point MockServer at the directory:
protoc --descriptor_set_out=service.dsc --include_imports service.proto
Then configure MockServer:
-Dmockserver.grpcDescriptorDirectory="/path/to/descriptors"
Or via environment variable:
MOCKSERVER_GRPC_DESCRIPTOR_DIRECTORY=/path/to/descriptors
Point MockServer at a directory of .proto source files and they will be compiled at startup using protoc:
-Dmockserver.grpcProtoDirectory="/path/to/protos"
This requires protoc to be available on the system PATH. If protoc is installed elsewhere, configure its path:
-Dmockserver.grpcProtocPath="/usr/local/bin/protoc"
Upload compiled descriptors at runtime via the REST API:
curl -v -X PUT "http://localhost:1080/mockserver/grpc/descriptors" \
--data-binary @service.dsc
When running MockServer in Docker, mount your proto files or descriptors into the container:
docker run -d --rm \
-p 1080:1080 \
-v /local/path/to/protos:/protos \
-e MOCKSERVER_GRPC_PROTO_DIRECTORY=/protos \
mockserver/mockserver:latest
Replace latest with a specific version tag (e.g. mockserver/mockserver:7.6.0) to pin a known working version.
Given a proto file such as:
syntax = "proto3";
package com.example.grpc;
service GreetingService {
rpc Greeting (HelloRequest) returns (HelloResponse);
rpc ListGreetings (HelloRequest) returns (stream HelloResponse);
}
message HelloRequest {
string name = 1;
}
message HelloResponse {
string greeting = 1;
}
You can create expectations that match on the JSON-converted request body and the gRPC metadata headers:
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.GreetingService/Greeting")
.withHeader("x-grpc-service", "com.example.grpc.GreetingService")
.withHeader("x-grpc-method", "Greeting")
.withBody(json("{\"name\": \"World\"}"))
)
.respond(
response()
.withStatusCode(200)
.withHeader("grpc-status", "0")
.withBody("{\"greeting\": \"Hello World\"}")
);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080).mockAnyResponse({
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/Greeting",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["Greeting"]
},
"body": {
"type": "JSON",
"json": "{\"name\": \"World\"}"
}
},
"httpResponse": {
"statusCode": 200,
"headers": {
"grpc-status": ["0"]
},
"body": "{\"greeting\": \"Hello World\"}"
}
}).then(
function () { console.log("expectation created"); },
function (error) { console.log(error); }
);
See REST API for full JSON specification
from mockserver import MockServerClient, HttpRequest, HttpResponse, KeyToMultiValue
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.GreetingService/Greeting",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.GreetingService"]),
KeyToMultiValue(name="x-grpc-method", values=["Greeting"])
],
body={"type": "JSON", "json": '{"name": "World"}'}
)
).respond(
HttpResponse(
status_code=200,
headers=[KeyToMultiValue(name="grpc-status", values=["0"])],
body='{"greeting": "Hello World"}'
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.GreetingService/Greeting',
headers: [
{ name: 'x-grpc-service', values: ['com.example.grpc.GreetingService'] },
{ name: 'x-grpc-method', values: ['Greeting'] }
],
body: { type: 'JSON', json: '{"name": "World"}' }
)
).respond(
HttpResponse.new(
status_code: 200,
headers: [{ name: 'grpc-status', values: ['0'] }],
body: '{"greeting": "Hello World"}'
)
)
import mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v7"
client := mockserver.New("localhost", 1080)
client.When(
mockserver.Request().
Method("POST").
Path("/com.example.grpc.GreetingService/Greeting").
Header("x-grpc-service", "com.example.grpc.GreetingService").
Header("x-grpc-method", "Greeting").
Body(`{"name": "World"}`),
).Respond(
mockserver.Response().
StatusCode(200).
Header("grpc-status", "0").
Body(`{"greeting": "Hello World"}`),
)
using MockServer.Client;
using MockServer.Client.Models;
using var client = new MockServerClient("localhost", 1080);
client.When(
HttpRequest.Request()
.WithMethod("POST")
.WithPath("/com.example.grpc.GreetingService/Greeting")
.WithHeader("x-grpc-service", "com.example.grpc.GreetingService")
.WithHeader("x-grpc-method", "Greeting")
.WithBody("{\"name\": \"World\"}")
).Respond(
HttpResponse.Response()
.WithStatusCode(200)
.WithHeader("grpc-status", "0")
.WithBody("{\"greeting\": \"Hello World\"}")
);
use mockserver_client::{ClientBuilder, HttpRequest, HttpResponse};
let client = ClientBuilder::new("localhost", 1080).build().unwrap();
client.when(
HttpRequest::new()
.method("POST")
.path("/com.example.grpc.GreetingService/Greeting")
.header("x-grpc-service", "com.example.grpc.GreetingService")
.header("x-grpc-method", "Greeting")
.body(r#"{"name": "World"}"#)
).respond(
HttpResponse::new()
.status_code(200)
.header("grpc-status", "0")
.body(r#"{"greeting": "Hello World"}"#)
).unwrap();
use MockServer\MockServerClient;
use MockServer\HttpRequest;
use MockServer\HttpResponse;
$client = new MockServerClient('localhost', 1080);
$client->when(
HttpRequest::request()
->method('POST')
->path('/com.example.grpc.GreetingService/Greeting')
->header('x-grpc-service', 'com.example.grpc.GreetingService')
->header('x-grpc-method', 'Greeting')
->jsonBody(['name' => 'World'])
)->respond(
HttpResponse::response()
->statusCode(200)
->header('grpc-status', '0')
->body('{"greeting": "Hello World"}')
);
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/Greeting",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["Greeting"]
},
"body": {
"type": "JSON",
"json": "{\"name\": \"World\"}"
}
},
"httpResponse": {
"statusCode": 200,
"headers": {
"grpc-status": ["0"]
},
"body": "{\"greeting\": \"Hello World\"}"
}
}'
For server streaming RPCs, use a gRPC stream response to return multiple messages. Each message can have an optional delay:
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.GreetingService/ListGreetings")
.withHeader("x-grpc-service", "com.example.grpc.GreetingService")
.withHeader("x-grpc-method", "ListGreetings")
)
.respondWithGrpcStream(
grpcStreamResponse()
.withStatusName("OK")
.withMessage("{\"greeting\": \"Hello Alice\"}")
.withMessage("{\"greeting\": \"Hello Bob\"}", delay(MILLISECONDS, 100))
.withMessage("{\"greeting\": \"Hello Charlie\"}", delay(MILLISECONDS, 200))
);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080).mockAnyResponse({
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/ListGreetings",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["ListGreetings"]
}
},
"grpcStreamResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"greeting\": \"Hello Alice\"}"},
{"json": "{\"greeting\": \"Hello Bob\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 100}},
{"json": "{\"greeting\": \"Hello Charlie\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 200}}
]
}
}).then(
function () { console.log("expectation created"); },
function (error) { console.log(error); }
);
See REST API for full JSON specification
from mockserver import MockServerClient, HttpRequest, KeyToMultiValue, GrpcStreamResponse, GrpcStreamMessage, Delay
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.GreetingService/ListGreetings",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.GreetingService"]),
KeyToMultiValue(name="x-grpc-method", values=["ListGreetings"])
]
)
).respond_with_grpc_stream(
GrpcStreamResponse(
status_name="OK",
messages=[
GrpcStreamMessage(json='{"greeting": "Hello Alice"}'),
GrpcStreamMessage(json='{"greeting": "Hello Bob"}', delay=Delay(time_unit="MILLISECONDS", value=100)),
GrpcStreamMessage(json='{"greeting": "Hello Charlie"}', delay=Delay(time_unit="MILLISECONDS", value=200))
]
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.GreetingService/ListGreetings',
headers: [
KeyToMultiValue.new(name: 'x-grpc-service', values: ['com.example.grpc.GreetingService']),
KeyToMultiValue.new(name: 'x-grpc-method', values: ['ListGreetings'])
]
)
).respond_with_grpc_stream(
GrpcStreamResponse.new(
status_name: 'OK',
messages: [
GrpcStreamMessage.new(json: '{"greeting": "Hello Alice"}'),
GrpcStreamMessage.new(json: '{"greeting": "Hello Bob"}', delay: Delay.new(time_unit: 'MILLISECONDS', value: 100)),
GrpcStreamMessage.new(json: '{"greeting": "Hello Charlie"}', delay: Delay.new(time_unit: 'MILLISECONDS', value: 200))
]
)
)
package main
import (
"bytes"
"net/http"
)
func main() {
body := []byte(`{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/ListGreetings",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["ListGreetings"]
}
},
"grpcStreamResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"greeting\": \"Hello Alice\"}"},
{"json": "{\"greeting\": \"Hello Bob\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 100}},
{"json": "{\"greeting\": \"Hello Charlie\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 200}}
]
}
}`)
req, _ := http.NewRequest("PUT",
"http://localhost:1080/mockserver/expectation",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
using System.Net.Http;
using System.Text;
using var httpClient = new HttpClient();
var json = @"{
""httpRequest"": {
""method"": ""POST"",
""path"": ""/com.example.grpc.GreetingService/ListGreetings"",
""headers"": {
""x-grpc-service"": [""com.example.grpc.GreetingService""],
""x-grpc-method"": [""ListGreetings""]
}
},
""grpcStreamResponse"": {
""statusName"": ""OK"",
""messages"": [
{""json"": ""{\""greeting\"": \""Hello Alice\""}""},
{""json"": ""{\""greeting\"": \""Hello Bob\""}"", ""delay"": {""timeUnit"": ""MILLISECONDS"", ""value"": 100}},
{""json"": ""{\""greeting\"": \""Hello Charlie\""}"", ""delay"": {""timeUnit"": ""MILLISECONDS"", ""value"": 200}}
]
}
}";
await httpClient.PutAsync(
"http://localhost:1080/mockserver/expectation",
new StringContent(json, Encoding.UTF8, "application/json")
);
use reqwest::blocking::Client;
let client = Client::new();
let body = r#"{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/ListGreetings",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["ListGreetings"]
}
},
"grpcStreamResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"greeting\": \"Hello Alice\"}"},
{"json": "{\"greeting\": \"Hello Bob\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 100}},
{"json": "{\"greeting\": \"Hello Charlie\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 200}}
]
}
}"#;
client.put("http://localhost:1080/mockserver/expectation")
.header("Content-Type", "application/json")
.body(body.to_string())
.send()
.unwrap();
// gRPC stream responses are not supported in the PHP client;
// use the REST API directly
$ch = curl_init('http://localhost:1080/mockserver/expectation');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => json_encode([[
'httpRequest' => [
'method' => 'POST',
'path' => '/com.example.grpc.GreetingService/ListGreetings',
'headers' => [
'x-grpc-service' => ['com.example.grpc.GreetingService'],
'x-grpc-method' => ['ListGreetings'],
],
],
'grpcStreamResponse' => [
'statusName' => 'OK',
'messages' => [
['json' => '{"greeting": "Hello Alice"}'],
['json' => '{"greeting": "Hello Bob"}', 'delay' => ['timeUnit' => 'MILLISECONDS', 'value' => 100]],
['json' => '{"greeting": "Hello Charlie"}', 'delay' => ['timeUnit' => 'MILLISECONDS', 'value' => 200]],
],
],
]]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json; charset=utf-8'],
]);
curl_exec($ch);
curl_close($ch);{% endraw %}
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.GreetingService/ListGreetings",
"headers": {
"x-grpc-service": ["com.example.grpc.GreetingService"],
"x-grpc-method": ["ListGreetings"]
}
},
"grpcStreamResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"greeting\": \"Hello Alice\"}"},
{"json": "{\"greeting\": \"Hello Bob\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 100}},
{"json": "{\"greeting\": \"Hello Charlie\"}", "delay": {"timeUnit": "MILLISECONDS", "value": 200}}
]
}
}'
Client streaming requests are converted with the combined stream messages in the request body. MockServer adds an x-grpc-client-streaming header to indicate this is a client streaming request. Client streaming support is limited — see Limitations below.
MockServer supports true bidirectional (bidi) gRPC streaming, where both the client and server can send messages independently and interleaved on a single stream. This requires the grpcBidiStreamingEnabled configuration flag to be set to true (default is false). See the gRPC Configuration section for details on enabling it.
A grpcBidiResponse action supports two response mechanisms:
Each reactive-rule response message can optionally be a response template by setting its templateType to VELOCITY or MUSTACHE. A templated response is rendered against the matched inbound message (exposed as the request body), so the reply can echo or derive fields from the request — for example {"greeting": "Hi $jsonPath.find(\"$.name\")"} — and can transition scenario state via $scenario.set('name','state') on an inbound match. Templating is opt-in: response messages with no templateType are sent exactly as written. JAVASCRIPT is not supported for bidi stream responses.
new MockServerClient("localhost", 1080)
.when(
request()
.withMethod("POST")
.withPath("/com.example.grpc.ChatService/BidiChat")
.withHeader("x-grpc-service", "com.example.grpc.ChatService")
.withHeader("x-grpc-method", "BidiChat")
)
.respondWithGrpcBidi(
grpcBidiResponse()
.withStatusName("OK")
.withMessage("{\"message\": \"Welcome to the chat!\"}")
.withRule(grpcBidiRule("{\"message\": \"hello\"}")
.withResponse("{\"message\": \"Hello! How can I help?\"}"))
.withRule(grpcBidiRule(".*goodbye.*")
.withResponse("{\"message\": \"Goodbye! Have a nice day.\"}"))
);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080).mockAnyResponse({
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.ChatService/BidiChat",
"headers": {
"x-grpc-service": ["com.example.grpc.ChatService"],
"x-grpc-method": ["BidiChat"]
}
},
"grpcBidiResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"message\": \"Welcome to the chat!\"}"}
],
"rules": [
{
"matchJson": "{\"message\": \"hello\"}",
"responses": [
{"json": "{\"message\": \"Hello! How can I help?\"}"}
]
},
{
"matchJson": ".*goodbye.*",
"responses": [
{"json": "{\"message\": \"Goodbye! Have a nice day.\"}"}
]
}
]
}
}).then(
function () { console.log("expectation created"); },
function (error) { console.log(error); }
);
See REST API for full JSON specification
from mockserver import (
MockServerClient, HttpRequest, KeyToMultiValue,
GrpcBidiResponse, GrpcBidiRule, GrpcStreamMessage,
)
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(
method="POST",
path="/com.example.grpc.ChatService/BidiChat",
headers=[
KeyToMultiValue(name="x-grpc-service", values=["com.example.grpc.ChatService"]),
KeyToMultiValue(name="x-grpc-method", values=["BidiChat"])
]
)
).respond_with_grpc_bidi(
GrpcBidiResponse(
status_name="OK",
messages=[GrpcStreamMessage(json='{"message": "Welcome to the chat!"}')],
rules=[
GrpcBidiRule(
match_json='{"message": "hello"}',
responses=[GrpcStreamMessage(json='{"message": "Hello! How can I help?"}')]
),
GrpcBidiRule(
match_json='.*goodbye.*',
responses=[GrpcStreamMessage(json='{"message": "Goodbye! Have a nice day."}')]
),
]
)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(
method: 'POST',
path: '/com.example.grpc.ChatService/BidiChat',
headers: [
KeyToMultiValue.new(name: 'x-grpc-service', values: ['com.example.grpc.ChatService']),
KeyToMultiValue.new(name: 'x-grpc-method', values: ['BidiChat'])
]
)
).respond_with_grpc_bidi(
GrpcBidiResponse.new(
status_name: 'OK',
messages: [GrpcStreamMessage.new(json: '{"message": "Welcome to the chat!"}')],
rules: [
GrpcBidiRule.new(
match_json: '{"message": "hello"}',
responses: [GrpcStreamMessage.new(json: '{"message": "Hello! How can I help?"}')]
),
GrpcBidiRule.new(
match_json: '.*goodbye.*',
responses: [GrpcStreamMessage.new(json: '{"message": "Goodbye! Have a nice day."}')]
)
]
)
)
package main
import (
"bytes"
"net/http"
)
func main() {
body := []byte(`{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.ChatService/BidiChat",
"headers": {
"x-grpc-service": ["com.example.grpc.ChatService"],
"x-grpc-method": ["BidiChat"]
}
},
"grpcBidiResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"message\": \"Welcome to the chat!\"}"}
],
"rules": [
{
"matchJson": "{\"message\": \"hello\"}",
"responses": [
{"json": "{\"message\": \"Hello! How can I help?\"}"}
]
},
{
"matchJson": ".*goodbye.*",
"responses": [
{"json": "{\"message\": \"Goodbye! Have a nice day.\"}"}
]
}
]
}
}`)
req, _ := http.NewRequest("PUT",
"http://localhost:1080/mockserver/expectation",
bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
http.DefaultClient.Do(req)
}
using System.Net.Http;
using System.Text;
using var httpClient = new HttpClient();
var json = @"{
""httpRequest"": {
""method"": ""POST"",
""path"": ""/com.example.grpc.ChatService/BidiChat"",
""headers"": {
""x-grpc-service"": [""com.example.grpc.ChatService""],
""x-grpc-method"": [""BidiChat""]
}
},
""grpcBidiResponse"": {
""statusName"": ""OK"",
""messages"": [
{""json"": ""{\""message\"": \""Welcome to the chat!\""}""}
],
""rules"": [
{
""matchJson"": ""{\""message\"": \""hello\""}"",
""responses"": [
{""json"": ""{\""message\"": \""Hello! How can I help?\""}""}
]
},
{
""matchJson"": "".*goodbye.*"",
""responses"": [
{""json"": ""{\""message\"": \""Goodbye! Have a nice day.\""}""}
]
}
]
}
}";
await httpClient.PutAsync(
"http://localhost:1080/mockserver/expectation",
new StringContent(json, Encoding.UTF8, "application/json")
);
use reqwest::blocking::Client;
let client = Client::new();
let body = r#"{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.ChatService/BidiChat",
"headers": {
"x-grpc-service": ["com.example.grpc.ChatService"],
"x-grpc-method": ["BidiChat"]
}
},
"grpcBidiResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"message\": \"Welcome to the chat!\"}"}
],
"rules": [
{
"matchJson": "{\"message\": \"hello\"}",
"responses": [
{"json": "{\"message\": \"Hello! How can I help?\"}"}
]
},
{
"matchJson": ".*goodbye.*",
"responses": [
{"json": "{\"message\": \"Goodbye! Have a nice day.\"}"}
]
}
]
}
}"#;
client.put("http://localhost:1080/mockserver/expectation")
.header("Content-Type", "application/json")
.body(body.to_string())
.send()
.unwrap();
// gRPC bidi responses are not supported in the PHP client;
// use the REST API directly
$ch = curl_init('http://localhost:1080/mockserver/expectation');
curl_setopt_array($ch, [
CURLOPT_CUSTOMREQUEST => 'PUT',
CURLOPT_POSTFIELDS => json_encode([[
'httpRequest' => [
'method' => 'POST',
'path' => '/com.example.grpc.ChatService/BidiChat',
'headers' => [
'x-grpc-service' => ['com.example.grpc.ChatService'],
'x-grpc-method' => ['BidiChat'],
],
],
'grpcBidiResponse' => [
'statusName' => 'OK',
'messages' => [
['json' => '{"message": "Welcome to the chat!"}'],
],
'rules' => [
[
'matchJson' => '{"message": "hello"}',
'responses' => [['json' => '{"message": "Hello! How can I help?"}']],
],
[
'matchJson' => '.*goodbye.*',
'responses' => [['json' => '{"message": "Goodbye! Have a nice day."}']],
],
],
],
]]),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => ['Content-Type: application/json; charset=utf-8'],
]);
curl_exec($ch);
curl_close($ch);{% endraw %}
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"method": "POST",
"path": "/com.example.grpc.ChatService/BidiChat",
"headers": {
"x-grpc-service": ["com.example.grpc.ChatService"],
"x-grpc-method": ["BidiChat"]
}
},
"grpcBidiResponse": {
"statusName": "OK",
"messages": [
{"json": "{\"message\": \"Welcome to the chat!\"}"}
],
"rules": [
{
"matchJson": "{\"message\": \"hello\"}",
"responses": [
{"json": "{\"message\": \"Hello! How can I help?\"}"}
]
},
{
"matchJson": ".*goodbye.*",
"responses": [
{"json": "{\"message\": \"Goodbye! Have a nice day.\"}"}
]
}
]
}
}'
Since gRPC requests are converted to JSON, all standard MockServer matchers work:
gRPC treats any metadata key whose name ends in -bin as binary: the value travels over the wire base64-encoded. MockServer never encodes or decodes these values — it passes them through exactly as written. So write the value already base64-encoded, both when matching a request and when setting a response header.
For example, to match metadata that your client sets as the four raw bytes 0x01 0x02 0x03 0x04, base64-encode them first and match on the result:
metadata key: x-trace-bin
raw bytes: 0x01 0x02 0x03 0x04
value to use: AQIDBA== (or AQIDBA — see below)
Padding does not matter. Most base64 encoders (including Java's Base64.getEncoder()) add = padding, producing AQIDBA==. The gRPC wire format omits that padding, so what actually arrives from a real gRPC client is AQIDBA. MockServer compares -bin values with the padding ignored, so both spellings match the same request and you do not have to know which one your client library produces.
This padding-insensitive comparison applies only to header names ending in -bin — the padding of any other header value is significant, as before.
Apart from the padding, a -bin value is compared exactly like any other header value. In particular header matching is case-insensitive, so two base64 values differing only in letter case are treated as equal even though they decode to different bytes. This is long-standing behaviour for every header, not something specific to -bin, but it is worth knowing when your metadata is binary.
For unary RPCs, set the gRPC status via the grpc-status response header (as shown in the unary example above). For server streaming RPCs, use the statusName field in the grpcStreamResponse action instead. You can also set a grpc-message header (unary) or statusMessage field (streaming) to provide error details. Standard gRPC status codes are supported:
| Code | Name |
|---|---|
| 0 | OK |
| 1 | CANCELLED |
| 2 | UNKNOWN |
| 3 | INVALID_ARGUMENT |
| 4 | DEADLINE_EXCEEDED |
| 5 | NOT_FOUND |
| 6 | ALREADY_EXISTS |
| 7 | PERMISSION_DENIED |
| 8 | RESOURCE_EXHAUSTED |
| 9 | FAILED_PRECONDITION |
| 10 | ABORTED |
| 11 | OUT_OF_RANGE |
| 12 | UNIMPLEMENTED |
| 13 | INTERNAL |
| 14 | UNAVAILABLE |
| 15 | DATA_LOSS |
| 16 | UNAUTHENTICATED |
As well as returning mocked responses, MockServer can forward a gRPC call to a real upstream gRPC server and record the exchange so it can later be replayed as a mock — the same record-then-mock workflow available for HTTP. This is useful for capturing real service behaviour once and then testing offline against the recording.
Forwarding happens when a gRPC request either matches a forward expectation, or (in proxy mode) matches no expectation at all. MockServer re-encodes the request back into gRPC protobuf framing, relays it upstream over HTTP/2, then decodes the framed response and returns it to your client — all transparently, so your gRPC client is unaware a proxy is in the path.
Forward a specific method to an upstream gRPC server:
new MockServerClient("localhost", 1080)
.when(
request()
.withPath("/com.example.grpc.GreetingService/Greeting")
)
.forward(
forward()
.withHost("upstream-grpc.example.com")
.withPort(50051)
);
var mockServerClient = require('mockserver-client').mockServerClient;
mockServerClient("localhost", 1080).mockAnyResponse({
"httpRequest": {
"path": "/com.example.grpc.GreetingService/Greeting"
},
"httpForward": {
"host": "upstream-grpc.example.com",
"port": 50051
}
}).then(
function () { console.log("expectation created"); },
function (error) { console.log(error); }
);
See REST API for full JSON specification
from mockserver import MockServerClient, HttpRequest, HttpForward
client = MockServerClient("localhost", 1080)
client.when(
HttpRequest(path="/com.example.grpc.GreetingService/Greeting")
).forward(
HttpForward(host="upstream-grpc.example.com", port=50051)
)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
client.when(
HttpRequest.new(path: '/com.example.grpc.GreetingService/Greeting')
).forward(
HttpForward.new(host: 'upstream-grpc.example.com', port: 50051)
)
import mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v7"
client := mockserver.New("localhost", 1080)
client.When(
mockserver.Request().Path("/com.example.grpc.GreetingService/Greeting"),
).Forward(
mockserver.Forward().
Host("upstream-grpc.example.com").
Port(50051),
)
using MockServer.Client;
using MockServer.Client.Models;
using var client = new MockServerClient("localhost", 1080);
client.When(
HttpRequest.Request().WithPath("/com.example.grpc.GreetingService/Greeting")
).Forward(
HttpForward.Forward()
.WithHost("upstream-grpc.example.com")
.WithPort(50051)
);
use mockserver_client::{ClientBuilder, HttpRequest, HttpForward};
let client = ClientBuilder::new("localhost", 1080).build().unwrap();
client.when(
HttpRequest::new().path("/com.example.grpc.GreetingService/Greeting")
).forward(
HttpForward::new("upstream-grpc.example.com", 50051)
).unwrap();
use MockServer\MockServerClient;
use MockServer\HttpRequest;
use MockServer\HttpForward;
$client = new MockServerClient('localhost', 1080);
$client->when(
HttpRequest::request()
->path('/com.example.grpc.GreetingService/Greeting')
)->forward(
HttpForward::forward()
->host('upstream-grpc.example.com')
->port(50051)
);
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d '{
"httpRequest": {
"path": "/com.example.grpc.GreetingService/Greeting"
},
"httpForward": {
"host": "upstream-grpc.example.com",
"port": 50051
}
}'
See REST API for full JSON specification
Provided a proto descriptor is loaded, the forwarded exchange is recorded with the decoded gRPC method path, status, and message JSON. Retrieve the recording as replayable expectations and re-apply it later to serve the same responses without the upstream:
// after driving traffic through the proxy, snapshot what was forwarded
Expectation[] recorded = new MockServerClient("localhost", 1080)
.retrieveRecordedExpectations(
request().withPath("/com.example.grpc.GreetingService/Greeting")
);
// (later, offline) replay the captured responses as mocks
new MockServerClient("localhost", 1080).upsert(recorded);
var mockServerClient = require('mockserver-client').mockServerClient;
var client = mockServerClient("localhost", 1080);
// after driving traffic through the proxy, snapshot what was forwarded
client.retrieveRecordedExpectations({
"path": "/com.example.grpc.GreetingService/Greeting"
}).then(function (recorded) {
// (later, offline) replay the captured responses as mocks
recorded.forEach(function (expectation) {
client.mockAnyResponse(expectation);
});
});
from mockserver import MockServerClient, HttpRequest
client = MockServerClient("localhost", 1080)
# after driving traffic through the proxy, snapshot what was forwarded
recorded = client.retrieve_recorded_expectations(
HttpRequest(path="/com.example.grpc.GreetingService/Greeting")
)
# (later, offline) replay the captured responses as mocks
client.upsert(*recorded)
require 'mockserver-client'
include MockServer
client = MockServer::Client.new('localhost', 1080)
# after driving traffic through the proxy, snapshot what was forwarded
recorded = client.retrieve_recorded_expectations(
request: HttpRequest.new(path: '/com.example.grpc.GreetingService/Greeting')
)
# (later, offline) replay the captured responses as mocks
client.upsert(*recorded)
import mockserver "github.com/mock-server/mockserver-monorepo/mockserver-client-go/v7"
client := mockserver.New("localhost", 1080)
// after driving traffic through the proxy, snapshot what was forwarded
recorded, _ := client.RetrieveRecordedExpectations(
mockserver.Request().Path("/com.example.grpc.GreetingService/Greeting"),
)
// (later, offline) replay the captured responses as mocks
client.Upsert(recorded...)
using MockServer.Client;
using MockServer.Client.Models;
using var client = new MockServerClient("localhost", 1080);
// after driving traffic through the proxy, snapshot what was forwarded
var recorded = client.RetrieveRecordedExpectations(
HttpRequest.Request().WithPath("/com.example.grpc.GreetingService/Greeting")
);
// (later, offline) replay the captured responses as mocks
client.Upsert(recorded.ToArray());
use mockserver_client::{ClientBuilder, HttpRequest};
let client = ClientBuilder::new("localhost", 1080).build().unwrap();
// after driving traffic through the proxy, snapshot what was forwarded
let recorded = client.retrieve_recorded_expectations(
Some(&HttpRequest::new().path("/com.example.grpc.GreetingService/Greeting"))
).unwrap();
// (later, offline) replay the captured responses as mocks
client.upsert(&recorded).unwrap();
use MockServer\MockServerClient;
use MockServer\HttpRequest;
use MockServer\Expectation;
$client = new MockServerClient('localhost', 1080);
// after driving traffic through the proxy, snapshot what was forwarded
$recorded = $client->retrieveRecordedExpectations(
HttpRequest::request()
->path('/com.example.grpc.GreetingService/Greeting')
);
// (later, offline) replay the captured responses as mocks
foreach ($recorded as $expectation) {
$client->upsertExpectation(Expectation::fromArray($expectation));
}
# after driving traffic through the proxy, snapshot what was forwarded
curl -v -X PUT "http://localhost:1080/mockserver/retrieve?type=RECORDED_EXPECTATIONS&format=JSON" -d '{
"path": "/com.example.grpc.GreetingService/Greeting"
}' > recorded-expectations.json
# (later, offline) replay the captured responses as mocks
curl -v -X PUT "http://localhost:1080/mockserver/expectation" -d @recorded-expectations.json
See REST API for full JSON specification
What is supported:
MockServer can inject gRPC-level faults — UNAVAILABLE, DEADLINE_EXCEEDED, RESOURCE_EXHAUSTED, and other status codes — into matched RPC calls, with optional latency and request-quota controls. Faults are registered per gRPC service name via a dedicated control-plane endpoint and apply before normal request conversion in GrpcToHttpRequestHandler.
This is separate from the health-check serving-status feature described below. See the gRPC Fault Injection section on the Chaos Testing page for the full profile reference and REST API examples.
Kubernetes readiness and liveness probes commonly use the gRPC Health Checking Protocol (grpc.health.v1.Health/Check) to verify that a service is ready to receive traffic. MockServer auto-responds to this well-known method without requiring a proto descriptor — protobuf encoding and decoding is handled manually so health checks work out of the box even when no descriptors have been loaded.
Health checking is always available — it is built in and cannot be disabled via a configuration property; MockServer answers any gRPC request whose content-type is gRPC and whose path matches the well-known health-check method. You can set the status for individual services, and for the overall server, via a REST endpoint.
A Check for a service that has never had a status registered fails with gRPC status NOT_FOUND, as the health-checking protocol requires — it does not report SERVING. This means a mistyped service name surfaces as an error rather than quietly looking healthy. Register each service whose health you want to check:
curl -v -X PUT "http://localhost:1080/mockserver/grpc/health" \
-H "Content-Type: application/json" \
-d '{"service": "my.payments.PaymentService", "status": "SERVING"}'
The overall-server health check — the one that uses the empty service name ("") — always answers and never returns NOT_FOUND. Its status defaults to SERVING and can be overridden. Note that setting the overall status applies only to that empty-name check; it is not a default inherited by named services.
Behaviour change. A Check for an unregistered service name previously returned the overall default status — usually SERVING — so a mistyped service name reported healthy, and a test asserting that a dependency was unhealthy passed without proving anything. If you relied on setting the overall status to cover every service, register each service explicitly with the PUT above.
| Value | Proto code | Meaning |
|---|---|---|
SERVING |
1 | The service is healthy and ready to accept requests (default) |
NOT_SERVING |
2 | The service is temporarily unavailable (probe will fail) |
UNKNOWN |
0 | Status is unknown |
SERVICE_UNKNOWN |
3 | The named service is not known to this server. Used by the streaming Watch method; a Check for an unknown service fails with gRPC status NOT_FOUND instead of returning this value |
Override the status for a named service:
curl -v -X PUT "http://localhost:1080/mockserver/grpc/health" \
-H "Content-Type: application/json" \
-d '{"service": "my.payments.PaymentService", "status": "NOT_SERVING"}'
Set service to the fully-qualified gRPC service name. Registering a service is also what makes it checkable at all — an unregistered name fails with NOT_FOUND. Use an empty string ("") to set the overall server status, which is the status reported to a health check that uses the empty service name; it is not inherited by named services. The response confirms the registration:
{ "status": "registered", "service": "my.payments.PaymentService", "servingStatus": "NOT_SERVING" }
Read all current status overrides:
curl -v "http://localhost:1080/mockserver/grpc/health"
Returns a JSON object mapping service names to their current status. The empty string key ("_default") shows the global default:
{
"_default": "SERVING",
"my.payments.PaymentService": "NOT_SERVING"
}
All status overrides are cleared on server reset. The GET endpoint returns only services that have had their status explicitly set, plus the global default.
When MockServer receives a request whose path is exactly /grpc.health.v1.Health/Check, the request is intercepted in GrpcToHttpRequestHandler before descriptor lookup — no proto descriptor for the health service is needed. GrpcHealthCheckHandler decodes the 5-byte gRPC frame header and then manually parses the protobuf HealthCheckRequest (field 1 = service name string). It then looks the service up in GrpcHealthRegistry. A named service that has no registered status fails the RPC with gRPC status NOT_FOUND and no message body; the empty service name always resolves, to the overall status. Otherwise the response is a manually-encoded gRPC-framed HealthCheckResponse (field 1 = status enum varint). The whole path bypasses the expectation matching engine so health checks always respond, even with no expectations registered.
MockServer supports the gRPC Server Reflection Protocol out of the box. Tools such as grpcurl and grpcui can use reflection to discover services and describe message types without needing a local .proto file.
Both the v1 and v1alpha reflection service paths are supported:
The reflection service answers from the proto descriptors already loaded into MockServer (via grpcDescriptorDirectory, grpcProtoDirectory, or the REST API upload). No additional configuration is required.
# List all services known to MockServer
grpcurl -plaintext localhost:1080 list
# Describe a specific service
grpcurl -plaintext localhost:1080 describe com.example.grpc.GreetingService
# Describe a message type
grpcurl -plaintext localhost:1080 describe com.example.grpc.HelloRequest
MockServer's gRPC path is buffered-unary: each HTTP/2 request carries exactly one gRPC message. The reflection handler therefore processes a single ServerReflectionRequest per call. This is sufficient for grpcurl list, single symbol lookups, and single file lookups. Fully interactive bidirectional-streaming reflection (a long-lived stream with multiple back-and-forth messages) is not supported by the current pipeline.
MockServer supports gRPC-Web, the variant of gRPC designed for browser clients and environments that cannot use HTTP/2 trailers. gRPC-Web requests are automatically detected and translated to standard gRPC for matching against existing expectations, so no additional configuration or separate expectations are needed.
When MockServer receives a request with a gRPC-Web content type:
gRPC-Web works over both HTTP/1.1 and HTTP/2, making it suitable for browser-based gRPC clients such as grpc-web and Improbable grpc-web.
The built-in gRPC health check (/grpc.health.v1.Health/Check), server reflection, and chaos fault injection all work transparently via gRPC-Web. No special configuration is needed.
The Connect protocol (used by connectrpc) is not currently supported. Connect uses a different framing format (JSON or proto over standard HTTP POST with application/connect+proto content type and trailers in a JSON envelope) that is distinct from gRPC-Web. If you need Connect support, please open a feature request.
gRPC works over HTTP/3 (QUIC) as well as HTTP/2 — set the http3Port configuration property to start the HTTP/3 listener and clients can make gRPC calls over QUIC. Unary, server-streaming, and bidirectional-streaming gRPC all work over HTTP/3 with the correct trailing-HEADERS grpc-status framing. As on the TCP path, bidirectional streaming requires the grpcBidiStreamingEnabled=true flag. Expectations are matched identically regardless of whether the call arrives over HTTP/2 or HTTP/3 — no separate or HTTP/3-specific expectations are needed.
See the HTTP/3 (QUIC) Support page for details on enabling and tuning the HTTP/3 listener.
For full details on gRPC configuration properties, see the gRPC Configuration section on the Configuration page.