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)
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/)
- One can configure HTTP port, set env
OPENMOCK_HTTP_PORT=80
- One can configure HTTP port, set env
- Kafka (optional)
- To enable mocking kafka, set env
OPENMOCK_KAFKA_ENABLED=true
. - One can also config
OPENMOCK_KAFKA_CLIENT_ID
andOPENMOCK_KAFKA_SEED_BROKERS
.
- To enable mocking kafka, set env
- AMQP (optional)
- To enable mocking amqp, set env
OPENMOCK_AMQP_ENABLED=true
- One can also config
OPENMOCK_AMQP_URL
.
- To enable mocking amqp, set env
Templates are YAML files that describe the behavior of OpenMock.
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
- 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
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"}}
# 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
# 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
# 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'
# 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 .
# 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
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()
}