Mock all the services. Intuitive YAML DSL for HTTP, Kafka, and AMQP mocks.
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.circleci
cmd/om
demo_templates
docs
vendor
.gitignore
Dockerfile
Gopkg.lock
Gopkg.toml
LICENSE
Makefile
README.md
amqp.go
docker-compose.yml
handle_actions.go
http.go
kafka.go
load.go
load_test.go
model.go
openmock.go
redis.go
redis_test.go
template.go
template_helper.go
template_helper_test.go
template_test.go

README.md

OpenMock

OpenMock is a Go service that can mock services in integration tests, staging environment, or anywhere. The goal is to simplify the process of writing mocks in various channels. Currently it supports three channels:

  • HTTP
  • Kafka
  • AMQP (e.g. RabbitMQ)

Usage

Use it directly with the go cli: om.

$ go get github.com/checkr/openmock/cmd/om
$ OPENMOCK_TEMPLATES_DIR=./demo_templates om

Use it with docker.

$ docker run -it -p 9999:9999 -v $(pwd)/demo_templates:/data/templates checkr/openmock 

Test it.

$ curl localhost:9999/ping

Dependencies.

  • HTTP (native supported, thanks to https://echo.labstack.com/)
  • Kafka (optional)
    • To enable mocking kafka, set env OPENMOCK_KAFKA_ENABLED=true.
    • One can also config OPENMOCK_KAFKA_CLIENT_ID and OPENMOCK_KAFKA_SEED_BROKERS.
  • AMQP (optional)
    • To enable mocking amqp, set env OPENMOCK_AMQP_ENABLED=true
    • One can also config OPENMOCK_AMQP_URL.

OpenMock Templates

Templates are YAML files that describe the behavior of OpenMock.

Templates Directory

You can put any number of .yaml or .yml files in a directory, and then point environment variable OPENMOCK_TEMPLATES_DIR to it. OpenMock will recursively (including subdirectories) load all the YAML files. For example:

# OPENMOCK_TEMPLATES_DIR=./demo_templates

./demo_templates
├── amqp.yaml
├── files
│   └── colors.json
├── http.yaml
├── jsonrpc.yaml
├── kafka.yaml
└── payload_from_file.yaml

Templates Schema

- key: name # the name of the mock

  ####################################################################
  ## Expect:
  ##   It represents the channel and condition for the mock.
  ##   For example, under what condition and from what channel should
  ##   we proceed the actions.
  ####################################################################
  expect:
    # Condition checks if we need to do the actions or not
    # It only proceeds if it evaluates to "true"
    condition: '{{.HTTPHeader.Get "X-Token" | eq "t1234"}}'
    # Use one (and only one) of the following channels - [http, kafka, amqp]
    http:
      method: GET
      path: /ping
    kafka:
      topic: hello_kafka_in
    amqp:
      exchange: exchange_1
      routing_key: key_in
      queue: key_in
  
  ####################################################################
  ## Actions:
  ##   Actions are a series of functions to run, which defines the
  ##   behaviors of the mock. Availabe actions are:
  ##     - publish_amqp
  ##     - publish_kafka
  ##     - redis
  ##     - reply_http
  ##     - send_http
  ##     - sleep
  ####################################################################
  actions:
    - publish_kafka:
        topic: hello_kafka_out
        payload: >
          {
            "kafka": "OK",
            "data": {}
          }
    - sleep:
        duration: 1s
    - reply_http:
        status_code: 200
        body: OK
        headers:
          Content-Type: text/html

Dynamic templating

OpenMock leverages https://golang.org/pkg/text/template/ to write dynamic templates. Specifically, it supports a lot of Context and Helper Functions.

  • Usage of {{ expr }}. One can put {{ expr }} inside three types of places:
    • expect.condition
    • action.http.body, action.kafka.payload, action.amqp.payload
    • action.http.body_from_file, action.kafka.payload_from_file, action.amqp.payload_from_file ({{ expr }} will be in the file)
  • Use Context inside {{ expr }}.
    .HTTPHeader      # type: http.Header; example: {{.HTTPHeader.Get "X-Token"}}
    .HTTPBody        # type: string;      example: {{.HTTPBody}}
    .HTTPPath        # type: string;      example: {{.HTTPPath}}
    .HTTPQueryString # type: string;      example: {{.HTTPQueryString}}
    
    .KafkaTopic      # type: string;      example: {{.KafkaTopic}}
    .KafkaPayload    # type: string;      example: {{.KafkaPayload}}
    
    .AMQPExchange    # type: string;      example: {{.AMQPExchange}}
    .AMQPRoutingKey  # type: string;      example: {{.AMQPRoutingKey}}
    .AMQPQueue       # type: string;      example: {{.AMQPQueue}}
    .AMQPPayload     # type: string;      example: {{.AMQPPayload}}
  • Use helper functions inside {{ expr }}. We recommend pipeline format (|) of the functions.
    # Supported functions defined in ./template_helper.go
    
      - jsonPath # doc: https://github.com/antchfx/xpath
      - xmlPath  # doc: https://github.com/antchfx/xpath
      - uuidv5   # uuid v5 sha1 hash
      - redisDo  # run redis commands. For example {{redisDo "RPUSH" "arr" "hi"}}
      - ...
    
    # Supported functions inherited from
    # https://github.com/Masterminds/sprig/blob/master/functions.go
    
      - replace
      - uuidv4
      - regexMatch
      - ...
    
    # Examples
    {{.HTTPHeader.Get "X-Token" | eq "t1234"}}
    {{.HTTPBody | jsonPath "user/first_name" | replace "A" "a" | uuidv5 }}
    {{.HTTPBody | xmlPath "node1/node2/node3"}}

Example: Mock HTTP

# demo_templates/http.yaml

- key: ping
  expect:
    http:
      method: GET
      path: /ping
  actions:
    - reply_http:
        status_code: 200
        body: OK
        headers:
          Content-Type: text/html

- key: header-token-ok
  expect:
    condition: '{{.HTTPHeader.Get "X-Token" | eq "t1234"}}'
    http:
      method: GET
      path: /token
  actions:
    - sleep:
        duration: 1s
    - reply_http:
        status_code: 200
        body: >
          { "hello": "you have a valid X-Token in the header" }

- key: header-token-not-ok
  expect:
    condition: '{{.HTTPHeader.Get "X-Token" | ne "t1234"}}'
    http:
      method: GET
      path: /token
  actions:
    - reply_http:
        status_code: 401

Example: Mock Kafka

# demo_templates/kafka.yaml

- key: test_kafka_1
  expect:
    kafka:
      topic: hello_kafka_in
  actions:
    - publish_kafka:
        topic: hello_kafka_out
        payload: >
          {
            "kafka": "OK",
            "data": {}
          }

- key: test_kafka_2
  expect:
    kafka:
      topic: hello_kafka_in
  actions:
    - publish_kafka:
        topic: hello_kafka_out
        payload_from_file: './files/colors.json' # the path is relative to OPENMOCK_TEMPLATES_DIR

Example: Mock AMQP (e.g. RabbitMQ)

# demo_templates/amqp.yaml

- key: test_amqp_1
  expect:
    amqp:
      exchange: exchange_1
      routing_key: key_in
      queue: key_in
  actions:
    - publish_amqp:
        exchange: exchange_1
        routing_key: key_out
        payload: >
          {
            "amqp": "OK",
            "data": {}
          }

- key: test_amqp_2
  expect:
    amqp:
      exchange: exchange_1
      routing_key: key_in
      queue: key_in
  actions:
    - publish_amqp:
        exchange: exchange_1
        routing_key: key_out
        payload_from_file: './files/colors.json'

Example: Use Redis for stateful things (by default, OpenMock uses an in-memory miniredis)

# demo_templates/redis.yaml

- key: hello_redis
  expect:
    http:
      method: GET
      path: /test_redis
  actions:
    - redis:
      - '{{.HTTPHeader.Get "X-TOKEN" | redisDo "SET" "k1"}}'
      - '{{redisDo "RPUSH" "random" uuidv4}}'
      - '{{redisDo "RPUSH" "random" uuidv4}}'
      - '{{redisDo "RPUSH" "random" uuidv4}}'
    - reply_http:
        status_code: 200
        body: >
          {
            "k1": "{{redisDo "GET" "k1"}}",
            "randomStr": "{{redisDo "LRANGE" "random" 0 -1}}",
            "random": [
              {{ $arr := redisDo "LRANGE" "random" 0 -1 | splitList ";;" }}
              {{ range $i, $v := $arr }}
                {{if isLastIndex $i $arr}}
                  "{{$v}}"
                {{else}}
                  "{{$v}}",
                {{end}}
              {{end}}
            ]
          }

# To test
# curl localhost:9999/test_redis -H "X-TOKEN:t123"  | jq .

Example: Send Webhooks

# demo_templates/webhook.yaml

- key: webhooks
  expect:
    http:
      method: GET
      path: /send_webhook_to_httpbin
  actions:
    - send_http:
        url: "https://httpbin.org/post"
        method: POST
        body: '{"hello": "world"}'
        headers:
          X-Token: t123
    - reply_http:
        status_code: 200
        body: 'webhooks sent'

# To test
# curl localhost:9999/send_webhook_to_httpbin

Advanced pipeline functions

To enable advanced mocks, for example, your own encoding/decoding of the kafka messages, one can develop by directly importing the github.com/checkr/openmock package.

For example:

package main

import "github.com/checkr/openmock"

func consumePipelineFunc(c *openmock.Context, in []byte) (out []byte, error) {
  return decode(in), nil
}

func publishPipelineFunc(c *openmock.Context, in []byte) (out []byte, error) {
  return encode(in), nil
}

func main() {
    om := openmock.OpenMock{
        KafkaConsumePipelineFunc: consumePipelineFunc,
        KafkaPublishPipelineFunc: publishPipelineFunc,
    }
    om.Start()
}