Examples

OpenMock examples

Runnable example documents covering scenarios, templating, delays, stateful polling, and all four protocols.

8 documents On GitHub

Eight worked openmock.yml documents, from a minimal REST mock to a two-server gRPC topology. Each one runs with the openmock-go reference server. Copy any of them and adapt.

01-minimal.yml

Minimal REST mock

A single health-check endpoint.

openmock: 0.2.0

# The smallest useful OpenMock document: one operation, one default scenario.
# Any GET /health request returns 200 with a native-YAML body.

info:
  title: Minimal REST mock
  version: 1.0.0
  description: A single health-check endpoint.

servers:
  - name: main
    type: http
    operations:
      - method: GET
        path: /health
        scenarios:
          - name: healthy
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                status: ok
02-scenarios-when.yml

Users API mock

Demonstrates when conditions and a default fallback.

openmock: 0.2.0

# Multiple scenarios evaluated first-match-wins. The most specific `when`
# scenarios come first; the conditionless default comes last.

info:
  title: Users API mock
  version: 1.0.0
  description: Demonstrates when conditions and a default fallback.

servers:
  - name: main
    type: http
    operations:
      - method: GET
        path: /users/{id}
        scenarios:
          # Matches only when the request carries an admin role header.
          - name: admin-user
            summary: Admin view — richer payload for callers with the admin role.
            when:
              headers:
                X-Role: admin
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                id: "{{params.id}}"
                name: Ada Lovelace
                role: admin

          # Matches a specific user id, regardless of headers.
          - name: known-user-1
            when:
              params:
                id: "1"
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                id: "1"
                name: Grace Hopper
                role: user

          # Default fallback — always matches, so it must be last.
          - name: default-user
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                id: "{{params.id}}"
                name: Anonymous
                role: user

      - method: POST
        path: /users
        scenarios:
          # Reject when the body is missing a required field.
          - name: missing-name
            when:
              body:
                name: ""
            response:
              status: 422
              headers:
                Content-Type: application/json
              body:
                error: name is required

          - name: created
            response:
              status: 201
              headers:
                Content-Type: application/json
              body:
                id: "100"
                name: "{{request.body.name}}"
                role: user
03-templating-faker.yml

Templating and faker demo

Shows every placeholder namespace defined in v0.1.

openmock: 0.2.0

# Templating placeholders and the faker namespace. Values are substituted from
# the incoming request (params/body/headers/query) and generated by faker.

info:
  title: Templating and faker demo
  version: 1.0.0
  description: Shows every placeholder namespace defined in v0.1.

servers:
  - name: main
    type: http
    operations:
      - method: GET
        path: /users/{id}
        scenarios:
          - name: rendered-user
            response:
              status: 200
              headers:
                Content-Type: application/json
                X-Request-Id: "{{request.headers.X-Request-Id}}"
              body:
                # From the path parameter.
                id: "{{params.id}}"
                # From a query parameter, e.g. /users/42?fields=full
                fields: "{{request.query.fields}}"
                # Generated by faker.
                name: "{{faker.person.fullName}}"
                email: "{{faker.internet.email}}"
                token: "{{faker.string.uuid}}"
                createdAt: "{{faker.date.recent}}"

      - method: POST
        path: /echo
        scenarios:
          - name: echo-body
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                # Reach into nested request body values with dotted paths.
                receivedName: "{{request.body.user.name}}"
                receivedRole: "{{request.body.user.role}}"
04-delays-errors.yml

Delays and errors

Latency simulation and error responses.

openmock: 0.2.0

# Response delays and author-declared error responses. Note the difference
# between a declared error (a scenario with a 4xx/5xx status) and OpenMock's
# own synthetic fallbacks (501 unmatched / 404 unrouted).

info:
  title: Delays and errors
  version: 1.0.0
  description: Latency simulation and error responses.

servers:
  - name: main
    type: http
    operations:
      - method: GET
        path: /orders/{id}
        scenarios:
          # Simulate a slow backend for a specific order.
          - name: slow-order
            when:
              params:
                id: "slow"
            response:
              status: 200
              delay: 1500
              headers:
                Content-Type: application/json
              body:
                id: slow
                status: processing

          # Simulate an upstream failure for a specific order.
          - name: failing-order
            when:
              params:
                id: "boom"
            response:
              status: 503
              delay: 250
              headers:
                Content-Type: application/json
                Retry-After: "5"
              body:
                error: upstream unavailable
                code: SERVICE_UNAVAILABLE

          # Not-found error for anything else.
          - name: not-found
            response:
              status: 404
              headers:
                Content-Type: application/json
              body:
                error: order not found
                id: "{{params.id}}"
05-polling-calls.yml

File-analysis polling mock

Simulates an async analyze-then-poll API.

openmock: 0.2.0

# Stateful polling with the `calls` facet. The engine keeps a call counter per
# operation and concrete path, so /v1/file/abc and /v1/file/def advance
# independently. `calls: 1` is shorthand for {min: 1, max: 1}; bounds are
# inclusive and either may be omitted ({min: 5} = the 5th call onward).
#
# Manual flow this mocks:
#   1. GET  /v1/file/{hash}   -> 404 (file unknown, 1st call)
#   2. POST /v1/file/upload   -> 202 (accepted for analysis)
#   3. GET  /v1/file/{hash}   -> 200 pending (calls 2-3)
#   4. GET  /v1/file/{hash}   -> 200 done (4th call onward, default scenario)

info:
  title: File-analysis polling mock
  version: 1.0.0
  description: Simulates an async analyze-then-poll API.

servers:
  - name: main
    type: http
    operations:
      - method: POST
        path: /v1/file/upload
        scenarios:
          - name: accepted
            response:
              status: 202
              headers:
                Content-Type: application/json
              body:
                status: queued

      - method: GET
        path: /v1/file/{hash}
        scenarios:
          - name: not-uploaded-yet
            when:
              calls: 1
            response:
              status: 404
              headers:
                Content-Type: application/json
              body:
                error: not_found

          - name: analyzing
            when:
              calls:
                min: 2
                max: 3
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                hash: "{{params.hash}}"
                status: pending

          # Default scenario: catches the 4th call and every one after it.
          # This is how "repeat the final response forever" is written.
          - name: done
            response:
              status: 200
              headers:
                Content-Type: application/json
              body:
                hash: "{{params.hash}}"
                status: done
                verdict: clean
06-grpc.yml

Order service gRPC mock

Unary calls, a streaming watch, errors, and calls-based polling.

openmock: 0.2.0

# gRPC mocks. Operations are addressed by service + rpc, declare their call
# type explicitly (unary | server-streaming), and match on metadata / message
# facets. Messages are native YAML following the proto3 JSON mapping (spec
# §4.2) — the mock is serveable without any .proto file.

info:
  title: Order service gRPC mock
  version: 1.0.0
  description: Unary calls, a streaming watch, errors, and calls-based polling.

# The schema behind this mock lives in 06-grpc.proto; 06-grpc.binpb is the
# descriptor set compiled from it (see the .proto header for the protoc/buf
# commands). Attaching it here is OPTIONAL and never changes what the document
# means — engines MAY ignore it, and MAY use it to serve binary protobuf,
# server reflection, or message validation (spec §3.4).
servers:
  - name: main
    type: grpc
    descriptorSet: ./06-grpc.binpb
    # Extension keys are permitted on server objects too (spec §11);
    # an engine that doesn't recognise this ignores it.
    x-acme-reflection: false
    operations:
      # Unary RPC with metadata matching and an error scenario.
      - service: shop.v1.OrderService
        rpc: GetOrder
        type: unary
        scenarios:
          - name: admin-view
            when:
              metadata:
                x-role: admin
            response:
              status: OK
              metadata:
                x-cache: MISS
              message:
                id: "{{request.message.orderId}}"
                state: SHIPPED
                internalNotes: fragile — handle with care

          - name: missing-order
            when:
              message:
                orderId: "0"
            response:
              status: NOT_FOUND
              error: order not found

          - name: default-view
            response:
              status: OK
              message:
                id: "{{request.message.orderId}}"
                state: SHIPPED

      # Server-streaming RPC: messages are emitted in order.
      - service: shop.v1.OrderService
        rpc: WatchOrder
        type: server-streaming
        scenarios:
          - name: lifecycle
            response:
              status: OK
              messages:
                - event: CREATED
                  orderId: "{{request.message.orderId}}"
                - event: PAID
                  orderId: "{{request.message.orderId}}"
                - event: SHIPPED
                  orderId: "{{request.message.orderId}}"

      # A stream that fails mid-way: two events, then the stream terminates
      # with a non-OK status.
      - service: shop.v1.OrderService
        rpc: WatchInventory
        type: server-streaming
        scenarios:
          - name: interrupted
            response:
              status: UNAVAILABLE
              error: inventory feed lost upstream connection
              messages:
                - level: "42"
                - level: "41"

      # The calls facet works on gRPC exactly as on HTTP; the counter is keyed by
      # service + rpc (no path parameters), i.e. per operation.
      - service: shop.v1.ReportService
        rpc: GetReport
        type: unary
        scenarios:
          - name: generating
            when:
              calls:
                max: 2
            response:
              status: OK
              message:
                state: GENERATING
          - name: ready
            response:
              status: OK
              message:
                state: READY
                url: https://example.com/report.pdf
Generate the descriptor set (06-grpc.binpb)
# The gRPC server's `descriptorSet` (spec §3.4) is the compiled
# FileDescriptorSet the transport uses to decode/encode protobuf.
# Generate it from the .proto with protoc:
protoc \
  --descriptor_set_out=06-grpc.binpb \
  --include_imports \
  06-grpc.proto
Protobuf schema (06-grpc.proto)
// The protobuf schema behind examples/06-grpc.yml.
//
// OpenMock documents never require a .proto file — messages are written as
// native YAML following the proto3 JSON mapping (spec §4.2). This file shows
// the schema that mock implements, and is the source for the descriptor set
// the document attaches via `grpc.descriptorSet` (spec §3.4):
//
//   protoc --include_imports --descriptor_set_out=06-grpc.binpb 06-grpc.proto
//   # or, with buf:
//   buf build 06-grpc.proto -o 06-grpc.binpb
//
// Note how the proto3 JSON mapping shapes the YAML in 06-grpc.yml:
//   - field names appear as their lowerCamelCase JSON names
//     (order_id here → orderId in the document),
//   - enum values appear by name (state: SHIPPED),
//   - 64-bit integers appear as decimal strings (level: "42").

syntax = "proto3";

package shop.v1;

service OrderService {
  rpc GetOrder(GetOrderRequest) returns (Order);
  rpc WatchOrder(WatchOrderRequest) returns (stream OrderEvent);
  rpc WatchInventory(WatchInventoryRequest) returns (stream InventoryLevel);
}

service ReportService {
  rpc GetReport(GetReportRequest) returns (Report);
}

enum OrderState {
  ORDER_STATE_UNSPECIFIED = 0;
  CREATED = 1;
  PAID = 2;
  SHIPPED = 3;
}

message GetOrderRequest {
  string order_id = 1;
}

message Order {
  string id = 1;
  OrderState state = 2;
  string internal_notes = 3;
}

message WatchOrderRequest {
  string order_id = 1;
}

message OrderEvent {
  OrderState event = 1;
  string order_id = 2;
}

message WatchInventoryRequest {}

message InventoryLevel {
  int64 level = 1;
}

enum ReportState {
  REPORT_STATE_UNSPECIFIED = 0;
  GENERATING = 1;
  READY = 2;
}

message GetReportRequest {}

message Report {
  ReportState state = 1;
  string url = 2;
}
07-graphql.yml

Order service GraphQL mock

Queries, a mutation, declared errors, and calls-based polling.

openmock: 0.2.0

# GraphQL mocks. Operations are addressed by operationType (query | mutation,
# query is the default) + operationName — the name a client gives its request
# document — and match on variables / headers facets. Responses carry `data`,
# `errors`, or both (a partial response); there is no status field, because
# GraphQL signals failure in-band (spec §7.4). The mock is serveable without
# any GraphQL schema.

info:
  title: Order service GraphQL mock
  version: 1.0.0
  description: Queries, a mutation, declared errors, and calls-based polling.

# The type system behind this mock lives in 07-graphql.graphql (SDL).
# Attaching it here is OPTIONAL and never changes what the document means —
# engines MAY ignore it, and MAY use it to serve introspection, validate
# incoming query documents, or check response shapes (spec §3.5).
servers:
  - name: main
    type: graphql
    schema: ./07-graphql.graphql
    # Extension keys are permitted on server objects too (spec §11);
    # an engine that doesn't recognise this ignores it.
    x-acme-introspection: false
    operations:
      # A query with header matching and a declared error scenario.
      # operationType defaults to query, but writing it is clearer.
      - operationType: query
        operationName: GetOrder
        scenarios:
          - name: admin-view
            when:
              headers:
                x-role: admin
            response:
              data:
                order:
                  id: "{{request.variables.orderId}}"
                  state: SHIPPED
                  internalNotes: fragile — handle with care

          # A partial response: `data` holds what resolved (a null order) and
          # `errors` explains what did not.
          - name: missing-order
            when:
              variables:
                orderId: "0"
            response:
              data:
                order: null
              errors:
                - message: order not found
                  path: [order]
                  extensions:
                    code: NOT_FOUND

          - name: default-view
            response:
              data:
                order:
                  id: "{{request.variables.orderId}}"
                  state: SHIPPED

      # A mutation. Routing considers operationType, so a mutation named like a
      # query would still resolve to the right operation.
      - operationType: mutation
        operationName: CreateOrder
        scenarios:
          - name: created
            response:
              data:
                createOrder:
                  id: "{{faker.string.uuid}}"
                  state: CREATED
                  note: "{{request.variables.input.note}}"

      # The calls facet works on GraphQL exactly as on HTTP and gRPC; the counter
      # is keyed by operationType + operationName, i.e. per operation.
      - operationType: query
        operationName: GetReport
        scenarios:
          - name: generating
            when:
              calls:
                max: 2
            response:
              data:
                report:
                  state: GENERATING
          - name: ready
            response:
              data:
                report:
                  state: READY
                  url: https://example.com/report.pdf
GraphQL schema (07-graphql.graphql)
# The GraphQL type system behind examples/07-graphql.yml.
#
# OpenMock documents never require a GraphQL schema — data, errors, and
# variables are written as native YAML (spec §4.3, §7.4). This file shows the
# schema that mock implements, and is what the document attaches via
# `graphql.schema` (spec §3.5). Engines MAY ignore it, and MAY use it to serve
# introspection, validate incoming query documents, or check response shapes.
#
# Note that the operationName each mock operation declares (GetOrder,
# CreateOrder, GetReport) is the name a *client* gives its request document —
# it does not appear in this schema. The root fields the clients select
# (order, createOrder, report) do.

type Query {
  order(id: ID!): Order
  report: Report!
}

type Mutation {
  createOrder(input: CreateOrderInput!): Order!
}

enum OrderState {
  CREATED
  PAID
  SHIPPED
}

type Order {
  id: ID!
  state: OrderState!
  internalNotes: String
  note: String
}

input CreateOrderInput {
  note: String
}

enum ReportState {
  GENERATING
  READY
}

type Report {
  state: ReportState!
  url: String
}
08-websocket.yml

Order feed WebSocket mock

A scripted feed — subscribe/ack, heartbeats, a close, and a per-connection welcome.

openmock: 0.2.0

# WebSocket mocks. Operations are addressed by the connection path (with
# {param} segments, as in HTTP); every message the client sends resolves
# against the operation's scenarios to a reply of zero or more messages,
# optionally followed by a close (spec §5.8, §7.5). v0.1 covers
# client-initiated exchanges only — the mock speaks only in reply.

info:
  title: Order feed WebSocket mock
  version: 1.0.0
  description: A scripted feed — subscribe/ack, heartbeats, a close, and a per-connection welcome.

servers:
  - name: main
    type: websocket
    operations:
      # Clients connect to /ws/orders/{id} and exchange JSON messages. The
      # connection-scoped facets (params, query, headers) match the values fixed
      # at establishment; message matches each inbound message.
      - path: /ws/orders/{id}
        scenarios:
          # The calls counter is keyed per (operation, connection), so every
          # connection's first message — whatever it is — is answered with a
          # welcome. Placed first so nothing below can consume call 1.
          - name: welcome
            when:
              calls: 1
            response:
              messages:
                - type: welcome
                  orderId: "{{params.id}}"

          # Heartbeats are deliberately ignored: an empty messages list replies
          # with nothing and keeps the connection open.
          - name: heartbeat
            when:
              message:
                type: ping
            response:
              messages: []

          # Admin connections (matched on the upgrade request's headers) get the
          # internal view.
          - name: admin-status
            when:
              headers:
                x-role: admin
              message:
                type: status
            response:
              messages:
                - type: status
                  orderId: "{{params.id}}"
                  state: SHIPPED
                  internalNotes: fragile — handle with care

          - name: status
            when:
              message:
                type: status
            response:
              messages:
                - type: status
                  orderId: "{{params.id}}"
                  state: SHIPPED

          # Reply, then close the connection normally.
          - name: bye
            when:
              message:
                type: bye
            response:
              messages:
                - type: goodbye
              close:
                code: 1000
                reason: client requested close

          # Default: an application-shaped error reply. WebSocket has no
          # per-message status; failures are in-band messages or a close with an
          # application code (4000–4999).
          - name: unknown-command
            response:
              messages:
                - type: error
                  error: unknown command
                  received: "{{request.message.type}}"