MockServer Ruby Client

Hand-written Ruby client for MockServer with full REST API, fluent builder DSL, and WebSocket callback support.

Installation

Add to your Gemfile:

gem 'mockserver-client', '~> 7.3'

Or install directly:

gem install mockserver-client

Quick Start

require 'mockserver-client'

# Create a client
client = MockServer::Client.new('localhost', 1080)

# Set up an expectation using the fluent API
client.when(
  MockServer::HttpRequest.request(path: '/hello')
    .with_method('GET')
).respond(
  MockServer::HttpResponse.response(body: 'world', status_code: 200)
)

# Verify a request was received
client.verify(
  MockServer::HttpRequest.request(path: '/hello'),
  times: MockServer::VerificationTimes.at_least(1)
)

# Clean up
client.reset
client.close

Block Form

MockServer::Client.new('localhost', 1080) do |client|
  client.when(
    MockServer::HttpRequest.request(path: '/api/test')
  ).respond(
    MockServer::HttpResponse.response(body: '{"status":"ok"}', status_code: 200)
  )
end
# Client is automatically closed when the block exits

WebSocket Callbacks

client = MockServer::Client.new('localhost', 1080)

# Response callback - dynamically generate responses
client.mock_with_callback(
  MockServer::HttpRequest.request(path: '/dynamic'),
  ->(request) {
    MockServer::HttpResponse.new(
      status_code: 200,
      body: "Echo: #{request.path}"
    )
  }
)

# Forward callback - modify requests before forwarding
client.mock_with_forward_callback(
  MockServer::HttpRequest.request(path: '/proxy'),
  ->(request) {
    request.with_header('X-Proxied', 'true')
  }
)

client.close

Models

All 26 domain model classes are available under the MockServer module:

  • Delay, Times, TimeToLive
  • KeyToMultiValue, Body, SocketAddress
  • HttpRequest, HttpResponse, HttpForward, HttpTemplate
  • HttpClassCallback, HttpObjectCallback, HttpError
  • HttpOverrideForwardedRequest, HttpRequestAndHttpResponse
  • ConnectionOptions
  • Expectation, ExpectationId
  • OpenAPIDefinition, OpenAPIExpectation
  • Verification, VerificationSequence, VerificationTimes
  • Ports
  • RequestDefinition (alias for HttpRequest)
  • Jwt (JWT / bearer-token request matcher)

JWT and combined body matchers

Match a request by the claims in its JWT (carried by default in the Authorization: Bearer <token> header). Each claim value is an exact string or a regular expression; a leading ! negates it (the same NottableString convention used elsewhere in MockServer). issuer, audience, algorithm, header and scheme are optional.

jwt = MockServer::Jwt.new(
  claims: {
    'sub'   => 'user-123',
    'role'  => '!admin',            # must NOT be "admin"
    'email' => '^.+@example.com$'   # regex
  },
  issuer:    'https://issuer.example.com',
  audience:  'my-api',
  algorithm: 'RS256'
)

request = MockServer::HttpRequest.new(method: 'GET', path: '/api')
                                 .with_jwt(jwt)

Combine several body matchers with Body.all_of — the request body must satisfy all of them:

body = MockServer::Body.all_of(
  MockServer::Body.json_path('$.name'),   # => {"type":"JSON_PATH","jsonPath":"$.name"}
  MockServer::Body.regex('.*active.*')    # => {"type":"REGEX","regex":".*active.*"}
)

request = MockServer::HttpRequest.new(method: 'POST', path: '/api').with_body(body)

LLM Mocking

Fluent builders under MockServer::LLM mock LLM provider endpoints. They produce expectations whose action is carried in the httpLlmResponse field; MockServer re-encodes the provider-agnostic completion into the configured provider's wire shape (OpenAI / Anthropic / Gemini / Bedrock / ...) when serving the request.

require 'mockserver-client'

client = MockServer::Client.new('localhost', 1080)

# A single completion mock
MockServer::LLM.llm_mock('/v1/chat/completions')
               .with_provider(MockServer::LLM::Provider::OPENAI)
               .with_model('gpt-4o')
               .responding_with(
                 MockServer::LLM.completion
                                .with_text('Hello!')
                                .with_usage(MockServer::LLM.usage.with_input_tokens(10).with_output_tokens(5))
               )
               .apply_to(client)

Multi-turn conversations use MockServer scenario-state advancement so each turn is served once, in order. Add per-turn predicates with when_* methods and optionally isolate per session with isolate_by:

MockServer::LLM.conversation
               .with_path('/v1/chat/completions')
               .with_provider(MockServer::LLM::Provider::OPENAI)
               .isolate_by(MockServer::LLM.header('x-session-id'))
               .turn.when_latest_message_contains('weather')
                    .responding_with(MockServer::LLM.completion.with_text('It is sunny.'))
               .turn.responding_with(MockServer::LLM.completion.with_text('Anything else?'))
               .apply_to(client)

Failover scenarios script N upstream failures (consecutive identical failures are coalesced) followed by a success; default JSON error bodies are generated per status code unless a custom body is supplied:

MockServer::LLM.llm_failover
               .with_path('/v1/chat/completions')
               .with_provider(MockServer::LLM::Provider::OPENAI)
               .fail_with(429, 2)          # two rate-limit failures
               .fail_with(503)             # then one service-unavailable
               .then_respond_with(MockServer::LLM.completion.with_text('Recovered'))
               .apply_to(client)

Each builder also exposes build to obtain the raw expectation Hash(es) without registering them.

MCP Mocking

MockServer::MCP.mcp_mock builds the set of expectations needed to emulate a Streamable-HTTP MCP (Model Context Protocol) server speaking JSON-RPC 2.0. It generates initialize, ping, notifications/initialized, and per-capability tools, resources, and prompts handlers.

MockServer::MCP.mcp_mock('/mcp')
               .with_server_name('MyServer')
               .with_tool('get_weather')
                 .with_description('Get the weather for a city')
                 .with_input_schema('{"type":"object","properties":{"city":{"type":"string"}}}')
                 .responding_with('72F and sunny')
               .and_then
               .with_resource('file:///config.json')
                 .with_name('config')
                 .with_content('{"debug":true}')
               .and_then
               .with_prompt('greet')
                 .with_argument('name', 'who to greet', true)
                 .responding_with('user', 'Hello there')
               .and_then
               .apply_to(client)

build returns the raw expectation Hashes if you prefer to register them yourself.

A2A Mocking

MockServer::A2A.a2a_mock builds the set of expectations needed to emulate an A2A (Agent-to-Agent) agent: an agent card served at /.well-known/agent.json plus JSON-RPC 2.0 tasks/send, tasks/get, and tasks/cancel over POST <path>. It optionally adds SSE streaming, push-notification config + delivery, per-message custom task handlers, and advertised skills.

MockServer::A2A.a2a_mock('/a2a')
               .with_agent_name('WeatherAgent')
               .with_agent_description('Forecasts the weather')
               .with_skill('weather')
                 .with_name('Weather lookup')
                 .with_tag('forecast')
                 .with_example('What is the weather in Paris?')
               .and_then
               .with_streaming
               .with_push_notifications('http://localhost:1234/a2a/callback')
               .on_task_send
                 .matching_message('forecast')
                 .responding_with('Sunny, 25C')
               .and_then
               .apply_to(client)

When push notifications are configured, each tasks/send both returns the JSON-RPC task response to the caller and POSTs the completed task to the webhook URL. As with MCP mocking, build returns the raw expectation Hashes if you prefer to register them yourself.

SRE Control Plane (SLO + Chaos)

Verify service-level objectives and drive scheduled chaos experiments against the running MockServer:

# Evaluate SLOs over a window. Returns the verdict Hash on PASS/INCONCLUSIVE;
# raises MockServer::VerificationError on FAIL (HTTP 406).
verdict = client.verify_slo(
  'name' => 'checkout-latency',
  'window' => 'PT5M',
  'minimumSampleCount' => 100,
  'objectives' => [
    { 'sli' => 'p99_latency_ms', 'comparator' => 'LESS_THAN', 'threshold' => 250 }
  ]
)

# Start a scheduled multi-stage chaos experiment (only one may be active).
client.start_chaos_experiment(
  'name' => 'payments-brownout',
  'stages' => [
    { 'durationMillis' => 60_000, 'profiles' => { 'payments.svc' => 'LATENCY' } }
  ]
)

SLO tracking must be enabled on the server (sloTrackingEnabled=true) or verify_slo raises MockServer::Error on HTTP 400.

Interactive Breakpoints

The client supports matcher-driven interactive breakpoints over the callback WebSocket. Register a breakpoint matcher to pause forwarded/proxied exchanges at specific phases and inspect/modify/continue them via callback handlers.

Register a breakpoint

client = MockServer::Client.new('localhost', 1080)

# REQUEST phase only
bp_id = client.add_request_breakpoint(
  MockServer::HttpRequest.new(path: '/api/.*'),
  ->(request) { request }  # continue unchanged (or return HttpResponse to abort)
)

# REQUEST + RESPONSE
bp_id = client.add_request_and_response_breakpoint(
  MockServer::HttpRequest.new(path: '/api/.*'),
  ->(request) { request },                      # REQUEST handler
  ->(request, response) { response }            # RESPONSE handler
)

# All phases with stream frame handler
bp_id = client.add_breakpoint(
  MockServer::HttpRequest.new(path: '/stream/.*'),
  %w[REQUEST RESPONSE RESPONSE_STREAM INBOUND_STREAM],
  request_handler: ->(request) { request },
  response_handler: ->(request, response) { response },
  stream_frame_handler: ->(frame) { { 'action' => 'CONTINUE' } }
  # Other actions: MODIFY (with body), DROP, INJECT (with body), CLOSE
)

Manage breakpoints

# List all matchers
matchers = client.list_breakpoint_matchers  # {"matchers" => [...]}

# Remove a specific matcher
client.remove_breakpoint_matcher(bp_id)

# Clear all matchers
client.clear_breakpoint_matchers

Start / Launch MockServer

The Ruby client can download and launch a local MockServer instance directly -- no Java installation and no Docker required. The launcher downloads a self-contained platform bundle (mockserver-<version>-<os>-<arch>) from the GitHub Release, verifies its SHA-256, caches it per-user, and starts it.

Quick start

require 'mockserver-client'

# Download (first run) and start MockServer on port 1080
handle = MockServer::BinaryLauncher.start(port: 1080)
puts "MockServer running on port #{handle.port}, PID #{handle.pid}"

# ... use MockServer ...

handle.stop

Just ensure the binary is present

launcher_path = MockServer::BinaryLauncher.ensure_launcher

Specify a version

handle = MockServer::BinaryLauncher.start(port: 1080, version: '7.4.0')

API reference

Method / Class Description
MockServer::BinaryLauncher.ensure_launcher(version:, log:) Download, verify, cache, and return the launcher path. Defaults to MockServer::VERSION.
MockServer::BinaryLauncher.start(port:, version:, extra_args:, log:) Ensure the binary and start MockServer. Returns a ServerHandle.
MockServer::BinaryLauncher::ServerHandle Handle to the running process. Methods: stop(timeout:), running?. Attributes: pid, port, launcher.

Supported platforms

OS Architecture
Linux x86_64, aarch64
macOS (darwin) x86_64, aarch64
Windows x86_64, aarch64

Environment variables

Variable Purpose
MOCKSERVER_BINARY_BASE_URL Mirror host for the release assets (corporate / air-gapped networks)
MOCKSERVER_BINARY_CACHE Override the cache directory (default: ~/.cache/mockserver/binaries on Unix)
MOCKSERVER_SKIP_BINARY_DOWNLOAD Fail instead of downloading (use with a pre-seeded cache in CI)

Version

By default the launcher downloads the MockServer version matching this client gem (currently MockServer::VERSION from lib/mockserver/version.rb). Pass an explicit version: keyword to override.

Using in tests (RSpec)

Require mockserver/rspec to get a shared context that provides a fresh MockServer::Client per example and resets the server before and after each example, so recorded requests, expectations and logs never leak between examples:

# spec_helper.rb
require 'mockserver/rspec'

# any spec tagged :mockserver gets a reset `mockserver` client
RSpec.describe 'my integration', :mockserver do
  it 'records the request' do
    # `mockserver` is the shared, reset client
    mockserver.when(
      MockServer::HttpRequest.request(path: '/hello')
    ).respond(
      MockServer::HttpResponse.response(body: 'world', status_code: 200)
    )
  end
end

Host and port default to 127.0.0.1:1080 and can be overridden with the MOCKSERVER_HOST / MOCKSERVER_PORT environment variables, or by defining a mockserver_host / mockserver_port let in your example group.

License

Apache-2.0

AI Assistant Integration

MockServer includes a built-in MCP (Model Context Protocol) server that enables AI coding assistants to create expectations, verify requests, and debug HTTP traffic programmatically.