Response
In the response part, you have to define the actual http response the stub server will serve when the stub matches the incoming http request as we defined it with request matching
Status
The http response status. In the range [100..599].
{
"response": {
"status": 200
}
}
Header
Http response headers. Note that keys are case-insensitive. Multivalued headers are not supported yet. You can
use response templating here as well if you add "transformers": ["response-template"].
{
"response": {
"transformers": [
"response-template"
],
"headers": {
"content-type": "application/json",
"ETag": "33a64df551425fcc55e4d42a148795d9f25f89d4",
"location": "{{request.url}}/1234"
}
}
}
Body
There are different ways to define a http response. We'll just focus here on supplying hardcoded values in the response, but you can relax all those fields with templates. We'll see that immediately in the next chapter.
{
"response": {
"body": "Hello World !",
"base64Body": "AQID",
"bodyFileName": "tests/stubs/response.json",
"jsonBody": {
"name": "john",
"surnames": [
"jdoe",
"johnny"
]
}
}
}
bodyuse this one if you have a text body or anything simple. If the body is large you'd better opt forbodyFileName.base64Bodyif the body is not utf-8 encoded use it to supply a body as byte. Those have to be base 64 encoded.bodyFileNamewhen the response gets large or to factorize some very common bodies, it is sometimes preferable to extract it in a file. When using it in a Rust project, the file path is relative to the workspace root. You can also use templating to dynamically select a file.jsonBodywhen the body is json. Even though such a body can be defined with all the previous fields, it is more convenient to define a json response body here.
Relaxed field
Using only hardcoded values is a good way to start mocking things. But as time goes on, your project might start to get bloated with a lot of stubs. You will also see the limit of hardcoded values when doing contract testing.
In order to "relax" your stub, you will have to use Handlebars helpers. They will allow you
to have random values generated for you, because, most of the time, that's what the actual application does. And, as a
consumer, you also don't care about the actual value of this field in your test i.e. "age": "{{anyU8}}" will work in
all your unit tests because none of your unit tests expects a particular value for this field.
In order to use a Handlebars helper, you need to add "transformers": ["response-template"].
Keep in mind that such helper will also be used to generate assertions when you will be using this stub for contract testing while verifying your producer.
NB: those templates are not available in Wiremock, you can only use them in stubr.
{
"response": {
"transformers": [
"response-template"
],
"jsonBody": {
"regex": "{{anyRegex '[a-z]{4}'}}",
"string": "{{anyNonEmptyString}}",
"alphanum": "{{anyAlphaNumeric}}",
"boolean": "{{anyBoolean}}",
"uuid": "{{anyUuid}}",
"ip": "{{anyIpAddress}}",
"host": "{{anyHostname}}",
"email": "{{anyEmail}}",
"enum": "{{anyOf 'alpha' 'beta' 'gamma'}}",
"number": "{{anyNumber}}",
"integer": "{{anyI32}}",
"float": "{{anyFloat}}",
"anyDate": "{{anyDate}}"
}
}
}
anyRegexgenerates a value matching this regex. Tip: most of the time will be used for strings but if this regex defines an integer, a float or a boolean and is used in"jsonBody""the generated value will be castanyNonEmptyStringoranyNonBlankStringgenerates an arbitrary utf-8 stringanyAlphaNumericgenerates an arbitrary string with only alphanumeric charactersanyBooleangenerates eithertrueorfalseanyUuidgenerates a random UUIDv4anyIpAddressgenerates a random IP address e.g.127.0.0.1anyHostnamegenerates an arbitrary hostname e.g.https://github.comanyEmailgenerates a random valid email address e.g.john.doe@gmail.comanyOfgiven the supplied values, will pick one randomly. Only works for strings.anyNumberwhen one does not care about the number size, generates either an integer or a floatanyI32oranyU32etc.. generates a random integer. Possible values are:anyU64,anyI64,anyU32,anyI32,anyU16,anyI16,anyU8,anyI8anyFloatgenerates a random floatanyDategenerates a date with formatyyyy-mm-ddanyTimegenerates a time with formathh:mm:ssanyDatetimegenerates a datetime with formatyyyy-mm-ddThh:mm:ssanyIso8601generates an iso-8601 compliant datetime
Response templating
Another kind of relaxing you can do is by being able to represent as best as possible the actual http response of your app. Very often, a field in the response is the exact same as the one in the request e.g. in a POST request to create a REST resource. You can use in your response parts of the request to do so.
{
"response": {
"transformers": [
"response-template"
],
"jsonBody": {
"url-path-and-query": "{{request.url}}",
"url-path": "{{request.path}}",
"url-path-segments": "{{request.pathSegments.[1]}}",
"query": "{{request.query.kind}}",
"multi-query": "{{request.query.kind.[1]}}",
"method": "{{request.method}}",
"header": "{{request.headers.Content-Type}}",
"multi-header": "{{request.headers.cache-control.[0]}}",
"body": "{{request.body}}",
"from-request": "{{jsonPath request.body '$.name'}}"
}
}
}
request.urlgiven a request tohttp://localhost/api/path?a=breturnspath?a=brequest.pathgiven a request tohttp://localhost/api/path?a=breturnsapi/pathrequest.pathSegments.[i]allows picking a part of the url path (iis zero indexed) e.g.http://localhost/a/b/cwithi== 1 returnsbquery.<selector>.[i]allows picking a named query parameter. Replace<selector>by the name of the query parameter. If the query parameter is multivalued, you can select only one with the zero indexedi. For example withhttp://localhost?a=1&a=2&a=3&b=1then{{query.b}}returns1and{{query.a.[1]}}returns2request.methodreturns the (uppercase) http request method. If you want the lowercase method just{{lower request.method}}request.headers.<selector>.[i]about the same as picking query parameters. Note that hereselectoris case-insensitive.request.bodytakes the raw request body without altering itjsonPath request.body '<json-path>'for templating only a field from request's json body.json-pathis the JSONPath for selecting the right field. Use an online JSONPath evaluator to try out your paths.
You also sometimes have to generate dynamic data or to transform existing one:
{
"response": {
"transformers": [
"response-template"
],
"jsonBody": {
"now": "{{now}}",
"now-fmt": "{{now format='yyyy/MM/dd'}}",
"now-fmt-epoch": "{{now format='epoch'}}",
"now-fmt-unix": "{{now format='unix'}}",
"now-positive-offset": "{{now offset='3 days'}}",
"now-negative-offset": "{{now offset='-3 days'}}",
"now-with-timezone": "{{now timezone='Europe/Rome'}}",
"number-is-odd": "{{isOdd request.body}}",
"number-stripes": "{{stripes request.body 'if-even' 'if-odd'}}",
"string-capitalized": "{{capitalize request.body}}",
"string-uppercase": "{{upper request.body}}",
"string-replace": "{{replace request.body 'a' 'b'}}",
"string-trim": "{{trim request.body}}",
"size": "{{size request.body}}",
"base64-encode": "{{base64 request.body padding=false}}",
"base64-decode": "{{base64 request.body decode=true}}",
"url-encode": "{{urlEncode request.header.x-raw}}",
"url-decode": "{{urlEncode request.header.x-encoded decode=true}}"
}
}
}
nowby default return the current datetime in RFC 3339 format (this is only for backward compatibility with Wiremock)formatcould be either:- a custom Java SimpleDateFormat (
for Wiremock compatibility) e.g.
format='yyyy/MM/dd' epochUnix timestamp in millisecondsunixUnix timestamp in seconds
- a custom Java SimpleDateFormat (
for Wiremock compatibility) e.g.
offsetnow with the given offset expressed in human-readable format. Refer to humantime documentation for further examples.timezonefor using a string timezone ( see list)
isOddorisEvenreturns a boolean whether the numeric value is an even or odd integercapitalizefirst letter to uppercase e.g.misterbecomesMister. There's also adecapitalizeto do the opposite.upperorlowerrecapitalizes the whole wordreplacefor replacing a pattern with given input e.g.{{replace request.body 'a' 'b'}}will replace all theain the request body withbstripesreturns alternate values depending if the tested value is even or oddtrimremoves leading & trailing whitespacessizereturns the number of bytes for a string (⚠️ not the number of characters) or the size of an arraybase64for standard (no base64 url encoding yet) Base64 encodingdecodefor decoding when truepaddingwith/without padding
urlEncodefor url encoding the value. Usedecode=trueto decode
Simulate fault
You can also use stubr to simulate http server runtime behaviour. And most of the time you'll want to introduce latencies to check how your consuming application reacts to such delays. Currently, the options are quite sparse but should grow !
{
"expect": 2,
"response": {
"fixedDelayMilliseconds": 2000
},
"delayDistribution": {
// a random delay with logarithmic distribution
"type": "lognormal",
"median": 100,
// The 50th percentile of latencies in milliseconds
"sigma": 0.1
// Standard deviation. The larger the value, the longer the tail
}
}
expectwill allow to verify that your unit test has not called the given stub more than N times. Turn it on like thisstubr::Stubr::start_with(stubr::Config { verify: true, ..Default::default() })or#[stubr::mock(verify = true)]with the attribute macrofixedDelayMillisecondsa delay (in milliseconds) added everytime this stub is matched. If you are using stubr standalone through the cli, this value can be either superseded by--delayor complemented by--latencydelayDistributionfor random delays (always in milliseconds), usetypeto choose the onelognormalis a pretty good approximation of long tailed latencies centered on the 50th percentile. Try different values to find a good approximation.median: the 50th percentile of latencies in millisecondssigma: standard deviation. The larger the value, the longer the tail.