Mock Definition
Mocks are written mainly using YAML.
A mock must respect this format:
- Common parts
- request:
# A "request" defines characteristics (method, path, query parameters...)
# that must be matched in order to return the associated response
context:
# A "context" (optional) defines particularities about the current mock,
# for instance it could help you define a mock that can only be called 3 times
- Static response:
- ... # common parts
response:
# A "response" will be returned as is when the client request matches
# the "request" declaration
- Dynamic response:
- ... # common parts
dynamic_response:
# A "dynamic_response" will be computed from the request made by the client
# when it matches the "request" declaration
- Proxy:
- ... # common parts
proxy:
# A "proxy" will redirect the call to another destination
Format of request
section
request
has the following format:
request:
# string matcher
method: PUT
# string matcher
path: /foo/bar
# multi map matcher
query_params:
limit: 10
offset: 0
filter: [foo, bar]
# multi map matcher
headers:
Content-Type: application/json
body:
# either string matcher
matcher: ShouldEqualJSON
value: >
{
"hello": "world"
}
# or body matcher
foo.bar:
matcher: ShouldMatch
value: baz.*
The above request will match the following request:
curl -XPUT \
--header 'Content-Type: application/json' \
--data '{ "hello": "world" }'
'localhost:8080/foo/bar?limit=10&offset=0&filter=foo&filter=bar'
As described above a request
is an object composed of matchers to apply on different fields of an HTTP request, such as the method, the path, the headers, etc.
Matchers are used to apply a predicate on a field. The most basic example of matcher is ShouldEqual
which is used to compare a field to a text. Smocker defines three types of matchers: string matcher, multi map matcher and body matcher.
String Matchers
String Matchers are used with the basic type string
. With the example of a field method
, a string matcher could be declared as follows:
method:
matcher: ShouldEqual
value: GET
Note that for convenience purposes the ShouldEqual
matcher can be simplified as just:
method: GET
Another example using the ShouldMatch
operator to match the methods PUT
and POST
could look like this:
method:
matcher: ShouldMatch
value: (PUT|POST) # This is a regular expression
Multi Map Matchers
Multi Map Matchers are used with the complex type map[string][]string
(map of strings). This complex type is used with query parameters and headers. This is because they can be declared multiple times. For example, the query string ?key=foo&key=bar&key=baz
is valid and would be represented as:
query_params:
key: [foo, bar, baz]
They could have the following format:
query_params:
key1: foo
key2: [foo, bar]
key3:
matcher: ShouldMatch
value: foo.*bar
key4:
- matcher: ShouldMatch
value: foo.*bar
key5:
- foo
- matcher: ShouldMatch
value: bar.*
- matcher: ShouldContainSubstring
value: baz
Body Matcher
Body Matcher is specific to the body
field.
It allows you to define matchers on keys of the JSON body.
If we take the following body as an example:
{
"foo": "barbaz",
"bar": {
"baz": ["test"]
}
}
We can define a matcher for the foo
key using:
body:
foo:
matcher: ShouldMatch
value: bar.*
Body Matcher uses stretchr/objx to query the keys of a JSON body.
So, if you want to make an assertion on the test
value in the body example above, you can define your matcher as follow:
body:
bar.baz[0]: test
or:
body:
bar.baz[0]:
matcher: ShouldEqual
value: test
Match Operators
The whole list of available matchers is:
Matcher | Description |
---|---|
ShouldEqual / ShouldNotEqual | Shallow equality check |
ShouldResemble / ShouldNotResemble | Deep equality check |
ShouldEqualJSON | JSON equality check |
ShouldContainSubstring / ShouldNotContainSubstring | Contains substring |
ShouldStartWith / ShouldNotStartWith | Starts with substring |
ShouldEndWith / ShouldNotEndWith | Ends with substring |
ShouldMatch / ShouldNotMatch | Regexp match |
ShouldBeEmpty / ShouldNotBeEmpty | Emptiness of a field |
Format of context
section
context
is optional in a mock. It has the following format:
context:
times: 5 # optional number, defines how many times the mock can be called
Format of response
section
response
has the following format:
response:
status: 200 # optional number (HTTP status code), defaults to 200
# a delay can be set through an optional duration (https://golang.org/pkg/time/#ParseDuration), defaults to 0
delay: 10s
# or you can also set min and max duration values in delay key to generate a random delay on each call
delay:
min: "0"
max: 10ms
headers: # optional map of string lists
Content-Type: application/json
body: > # optional string
{
"data": ["foo", "bar"]
}
The response is returned as is when the request matches. Note that YAML allows multi-line strings, which is very useful for writing JSON bodies.
Format of dynamic_response
section
dynamic_response
is a way to generate a response depending on data from the request. This is a powerful feature when used with the request matchers. Dynamic responses can be generated in two different ways:
- Go templates
- Lua scripts
It has the following format:
dynamic_response:
engine: # mandatory string, the engine to use to generate the response
script: # mandatory string, the script generating the response
Dynamic responses using Go templates
A dynamic response using Go templates must generate a YAML string representing a response
object. Some parts of the response can be generated dynamically. In Go templates, the Request
variable is available, containing the values of the current request. The utility library Masterminds/sprig is also fully available.
The easiest way to write a dynamic response using Go templates is to first write the static response you want:
response:
headers:
Content-Type: [application/json]
body: >
{
"message": "request path: /foo/bar"
}
and to copy it in the script
field of a dynamic_response
:
dynamic_response:
engine: go_template # mandatory string, indicates that the dynamic response must use the Go template engine
script: > # mandatory string
headers:
Content-Type: [application/json]
body: >
{
"message": "request path: {{.Request.Path}}"
}
We also replaced the static /foo/bar
with {{.Request.Path}}
in order to make the generated response dynamic depending on the called path.
Note that the script
is a string which must be written in YAML.
Dynamic responses using Lua scripts
A dynamic response using Lua must generate a Lua table representing a response
object. In Lua scripts, the request
variable is available, containing the values of the current request. The math
, string
, and table
libraries are available.
A dynamic response using Lua scripts has the following format:
dynamic_response:
engine: lua
script: >
require "math"
return {
body = {
message = "request path: "..request.path
},
delay = {
min = "0ms",
max = "10ms"
},
headers = {
["Content-Type"] = {"application/json"}
}
}
Tips:
- In associative tables, keys containing a
-
(such asContent-Type
) must be wrapped:{ ["Content-Type"] = ... }
- You can write multi-line strings (for the
body
for instance) using double square braces instead of quotes:[[ ... ]]
- If you already have a JSON body, you can convert it to a Lua table using any existing converter
Format of proxy
section
proxy
is a way to redirect calls to another destination. It's a key feature, when you want to use Smocker as an API gateway.
Basically, proxy mocks will be used to configure the way Smocker redirect calls between your services.
It has the following format:
proxy:
host: # destination host
follow_redirect: # optional boolean
skip_verify_tls: # optional boolean
keep_host: # optional boolean
headers: # optional map of string lists
Forwarded: "for=unknown;host=www.example.com;proto=http"
By default, redirect responses from the destination host are returned as any other response. Setting follow_redirect
to true
makes Smocker follow any redirect response before responding.
If you need to deal with hosts using HTTPS and self-signed certificates, you can define the proxy mock as insecure by setting skip_verify_tls
to true
.
Host header is overriden using destination host value by default. With keep_host
set to true
, request sent to the
destination host have same Host
HTTP header as incoming request.
Headers defined in the proxy mock definition are injected in the request sent to the destination host. If the header is already present in the incoming request, its value will be overwritten.