Ekko Proxy WireMock Editor
The Ekko Proxy WireMock Editor lets you manage wiremocks for the proxy instances. The wiremock configuration files created by the editor are not just available to the proxy instances but can also be used in WireMock based unit tests.
Managing WireMocks
The Mock Editor is accessed from the navigation pane and lists any virtual mocks that are available to the proxy instances:
Available WireMocks will appear in the table as shown above and are stored in a mocks folder (relative to the application).
Fields and functions
The fields and functions of this part of the WireMock management view are as follows:
Field | Description |
---|---|
Search | The search field can be used to search the table showing the available mocks. Pressing the Enter key or clicking the search button will initiate the search and filter the table showing only matches. The table header will show if it's filtered or not by displaying the filtered icon: |
Link status | A green link means the Ekko Proxy client is connected to the Ekko Proxy Server. A red link means the Ekko Proxy client has been disconnected from the Ekko Proxy Server. |
Import/Export mocks | Click to select import / export option from menu: : Import Mocks... Shows import mocks dialog where Ekko Proxy mock files (*.json or a zip file containing one or more of the same in a mocks folder) can be dragged & dropped for import. Alternatively a mocks folder can be selected for import. : Export Mocks Exports any Ekko Proxy mock files (located in /mocks/*.json) to your browser downloads directory as a zip file. |
Compare | The 'Compare' button provides the option to compare two mock files in the Compare dialog. Note this button is only enabled when exactly two mocks are selected. |
Open | The 'Open' button opens a mock file in a new browser tab / window. Note depending on browser settings then you may need to enable pop-ups for the Ekko Proxy client as otherwise the action may be blocked by the browser. |
Delete | Deletes the selected mock file(s) from disk. Note this cannot be undone! |
Mocks table
The mocks table contain the following columns:
Field | Description |
---|---|
Name / Id | The name or id of the wiremock. The unique id is shown if the mock has no name. |
Scenario | The scenario name of the wiremock if any. |
Method | The HTTP request method of the wiremock e.g. GET, POST etc. |
Matcher | The matcher type used to match the url. |
Url | The request url for the wiremock. |
Status | The HTTP response status of the wiremock e.g. 200 |
Filename | The filename of the wiremock json file. |
Note multiple messages can be selected by clicking the check box or by holding down the Ctrl key while clicking on a mock row.
Mock details
Mock details of a mock (either selected from the mocks table or directly entered) are displayed in the mock details section as shown below.
The fields on the mock details section are described below:
Field | Description |
---|---|
Mock Name | The name of the wiremock. |
Priority | The priority of the wiremock from 1 to 10 with 1 being highest and 10 lowest priority. Defaults to 5. Note if a wiremock has a priority of 10 then in proxy playback mode the mock will only be considered if there is no matching recording to serve instead. Mocks with higher priority than 10 (i.e. 1 to 9) will always be considered before any proxy recordings. |
Global | If checked the wiremock is available to all proxy instances. If unchecked then the Proxy Port field needs to be specified making the wiremock available to the proxy instance running on that port only. |
Proxy Port | The proxy listening port [1 - 65535] that the wiremock should be available to. Can only be filled if the Global field is unchecked. |
Scenario Name | The scenario name of the wiremock if any. |
Required Scenario State | The required scenario state of the wiremock if any. Note the starting state for a scenario should be 'Started'. |
New Scenario State | The new scenario state to set if any. |
Request details
The request details section shows the request matching details of a mock:
The fields and functions of this part of the mock management view are:
Field | Description |
---|---|
Method | The HTTP method that a request needs to match for this mock. |
Url Matcher | The url matcher type for matching this mock. The matching types are: |
Url Value | The request url / pattern value to match. |
HEADERS | The headers tab allows for matching on one or more request header values. Name - the name of the header to look for. Matching - the type of match to try and match the value with: Value - the header value to match. Action - the actions for the row: move row up, move row down, delete row. |
QUERY PARAMS | The query params tab allows for matching on one or more request query parameter values. Name - the name of the query parameter to look for. Matching - the type of match to try and match the value with. See description of match type above. Value - the query parameter value to match. Action - the actions for the row: move row up, move row down, delete row. |
COOKIES | The cookies tab allows for matching on one or more request cookie values. Name - the name of the cookie to look for. Matching - the type of match to try and match the value with. See description of match type above. Value - the cookie value to match. Action - the actions for the row: move row up, move row down, delete row. |
BODY PATTERNS | The body patterns tab allows for matching on one or more request body values. Matching - the type of match to try and match the value with: Value - the body value to match. Action - the actions for the row: move row up, move row down, delete row. |
Add row | Adds a new row for the currently selected tab. |
You can find further details on request matching here: WireMock request matching.
Response details
The response details section shows what the response of the mock will be:
The fields and functions of this part of the mock management view are:
Field | Description |
---|---|
Status | The HTTP response status for this mock. |
Status Message | The HTTP response status message. |
HEADERS | The headers tab allows for entering one or more response headers. Name - the name of the response header. Value - the response header value. Action - the actions for the row: move row up, move row down, delete row. |
Add row | Adds a new row for the response headers. |
BODY | The HTTP response body. |
Base64 | If checked the response body should be base64 encoded. |
Templating | If checked the response body can contain Handlebars template code e.g. {{request.path.[0]}}. See the Response Templating section for more details. |
DELAY | The delay tab contains options for setting mock level response delays. See the Mocking Delays section for more details. |
FAULT | The fault tab contains options for setting mock fault responses. See the Mocking Faults section for more details. |
UPDATE MOCK | The Update Mock button will be available if a mock is selected in the mocks table
allowing for the mock details to tbe edited and saved. Note when updating a mock the change will not affect running proxies until these are stopped and started again. |
ADD NEW MOCK | The Add New Mock button will create a new mock from the mock details. Note when adding a mock the new mock will not be available to running proxies until these are stopped and started again. |
For further information on WireMock please refer to the WireMock documentation.
Response Templating
You can send back different responses by configuring different mocks that matches on different request properties. This works well in simple scenarios but can get a bit cumbersome depending on the number of different responses required. For these use cases you can take advantage of the templating option instead which allows you to define handlebars type expressions to output dynamic values based on the request. These expressions can be used in the response headers as well as the response body. For more information on handlebars see handlebars guide.
To enable templating simply tick the Templating checkbox.
The table below shows the request object properties that are available for templating.
Request Property | Description |
---|---|
request.url | The URL path and query e.g. /api/todo?param=foo |
request.path | URL path e.g. /api/todo |
request.pathSegments.[<n>] | URL path segment (zero indexed) e.g. request.pathSegments.[2] |
request.query.<key>> | First value of a query parameter e.g. request.query.search |
request.query.<key>.[<n>] | The nth value of a query parameter (zero indexed) e.g. request.query.search.[5] |
request.method | The request method e.g. POST |
request.host | The hostname part of the URL e.g. my.site.com |
request.port | The port number e.g. 8080 |
request.scheme | The protocol part of the URL e.g. http or https |
request.baseUrl | The URL up to the start of the path e.g. https://my.site.com:8080 |
request.headers.<key> | First value of a request header e.g. request.headers.X-Request-Id |
request.headers.[<key>] | Header with awkward characters e.g. request.headers.[$?blah] |
request.headers.<key>.[<n>] | The nth value of a header (zero indexed) e.g. request.headers.ManyThings.[1] |
request.cookies.<key> | First value of a request cookie e.g. request.cookies.JSESSIONID |
request.cookies.<key>.[<n>] | The nth value of a request cookie e.g. request.cookies.JSESSIONID.[2] |
request.body | The request body text (avoid for non-textual bodies) |
Dynamic expressions using Handlebars are denoted using double curly brackets e.g. {{request.path}} would output the request path. There are also a number of HTTP elements like query parameters, form fields and headers that can be single or multiple valued. The template request model and built-in helpers attempt to make this easy to work with by wrapping these in a list or single type that returns the first (and often the only) value when no index is specified, but there is also support index access. For instance, given a request URL like /multi-query?myparams=1&myparams=2&myparams=3 then you can extract the query data in the following ways:
{{request.query.myparams}} {{!- Will return 1 --}} {{request.query.myparams.0}} {{!- Will return 1 --}} {{request.query.myparams.first}} {{!- Will return 1 --}} {{request.query.myparams.1}} {{!- Will return 2 --}} {{request.query.myparams.[-1]}} {{!- Will return 2 --}} {{request.query.myparams.last}} {{!- Will return 3 --}}
Note when using the Handlebars eq helper with single or multiple valued elements, it is necessary to use the indexed form, even if only one value is present. The reason for this is that the non-indexed form returns the wrapper type and not a String type, and will therefore fail to compare with another String value.
In Handlebars there are special characters that have special meaning and therefore can't be used in key names when referencing values. If you need to access keys containing these characters you can use the lookup helper, which permits you to pass the key name instead as a string literal and thus avoid the restriction. One common example of this issue is with array-style query parameters, so for instance if your request URLs that you're matching are of the form /myapi?ids[]=111&ids[]=222&ids[]=333 then you can access these values like:
{{lookup request.query 'ids[].1'}} {{!- Will return 2 --}}
Handlebars Helpers
In addition to the standard helpers and template functions provided by the Java Handlebars implementation by jknack (see guide), EkkoProxy supports a number of convenience helpers for simulating delays and faults, working with JSON or XML request bodies, date/time etc. as described in the following sections.
Delay & fault
Description | Example(s) |
---|---|
The delay helper allows for setting delays programmatically. The types of delays possible are: Mocking Delays section for details. | You can also set delays easily using the Delay tab settings - see the
In the simplest form you can just write:{{delay}} or {{delay type='fixed'}} , this would add a delay of 100ms - the default value of the wait parameter. By adding the wait parameter you can then specify a fixed delay in milliseconds as seen below: {{delay wait=190}} or {{delay type='fixed' wait=190}} For a random uniform delay simply specify the type, lower and upper bounds: {{delay type='uniform' lower=200 upper=600}} , this would simulate a stable latency of 400ms +/- 200ms. The default values for the lower and upper bounds are 100ms and 500ms respectively which will be used if omitted. A log-normal delay can be set as follows: {{delay type='lognormal' median=500 sigma=0.5}} , the median is the 50th percentile of the distribution in milliseconds and sigma is the standard deviation of the distribution - a larger value results in a longer tail. The defaults for median and sigma are 500ms and 0.4 respectively. Lastly there is an option to print out the actual delay value used at the time by setting print to true. The example below only sets and displays the delay value if the JSON request body contains a top level age property with the value of 18. {{#eq (jsonPath request.body '$.age') 18}}
|
The fault helper let you set faults programmatically. The types of faults supported are: Mocking Faults section for details. | You can also set faults easily using the Fault tab settings - see theYou can set a fault by simply adding the following to the body template:{{fault}} , this would trigger a fault of the empty type as the default. When a fault is fired the template processing stops and the fault is immediately returned as the response. To specify a different fault type just add the type property e.g. {{fault type="malformed"}} Note - the empty, malformed, random and reset fault types take no parameters. You can further surround faults by conditions to control when they should trigger. For instance, to make the malformed fault have a 50% chance of triggering you can use it in combination with the randomInt helper: {{#lt (randomInt lower=0 upper=99) 50}} In another example with the given JSON: { "name": "FooBar", "age": 25 }You could set a http type error to fire with a JSON error response as per below: {{#eq (jsonPath request.body '$.age') 25}} The http fault type can have the following parameters:
{{fault type='http' status=549 reason'my reason' body='{"error":"101"}' contentType='application/json'}} The last fault type is the mock type which allows you to refer to another mock's response. It takes one mandatory parameter id which should be the universally unique identifier (UUID) of an existing mock. If the id does not match a mock, say the mock referenced has been deleted or you made a typo, then a HTTP 404 will be sent instead. An example of this fault can be seen below. {{fault type="mock" id="b3146b5d-e470-4090-b355-3d9a5dcae227"}} You can find the UUID of a mock just after the Mock Details label just above the Mock Name field as shown below. |
JSON & XML
Description | Example(s) |
---|---|
The jsonPath helper can be used, when a request body contains JSON, to extract values or sub documents using JSONPath expressions. | Given the JSON:
{ id: "123", "details": { "name": "FooBar", "age": 36 } }, the expression {{jsonPath request.body '$.details.name'}} would output 'FooBar'.An example of using conditional logic to return different response based on properties in request message can be seen below. {{#gt (jsonPath request.body '$.details.age') 18}}You can enter{{else}}Should be 18 or above.{{/gt}}} With the given request body above having age equal to 36 the expression would print 'You can enter'. It is also possible to specify a default value if the path evaluates to null or undefined as per below example. {{jsonPath request.body '$.shirtSize' default='M'}}
|
The parseJson helper will parse a given input into a map-of-maps. It can also assign the result to a variable if a name is specified, and if not the result is returned instead. |
It can accept the JSON in various ways - e.g. from an inline block:
{{#parseJson 'parsedObj'}} { "name": "transformed" } {{/parseJson}}, this would enable you to access the object as usual e.g. {{parsedObj.name}} Or as a parameter: {{parseJson request.body 'bodyJson'}} , which you can use like: {{bodyJson.name}} And without assigning to a variable you could do: {{lookup (parseJson request.body) 'name'}}
|
The xPath helper allows you, if the request body contains XML, to extract values or sub documents using an XPath expression. | Given the XML:
<foo> <bar>Some value</bar> <things> <item id="123456">Item One</item> <item id="654321">Item Two</item> </things> </foo>, the expression {{xPath request.body '/foo/bar/text()'}} would render 'Some value'.And the following would iterate the item nodes and print the node name, node value and id of each item node.
{{#each (xPath request.body '/foo/things/item') as |node|}}
|
The soapXPath helper is an added convenience helper for extracting values from SOAP bodies e.g. for the SOAP document. | Given the SOAP message:
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope/"> <soap:Body> <m:person> <m:name>FooBar</m:name> </m:person> </soap:Body> </soap:Envelope>, the following expressions would both render 'FooBar' in the output. {{soapXPath request.body '/person/name/text()'}} or
{{soapXPath request.body './/name/text()'}}
|
Date & time
Description | Example(s) |
---|---|
The now helper renders the current date/time, with the ability to specify the format and offset. The format can be specified as per Java date formats, with the addition of 'epoch' and 'unix'. |
Assuming today is 29th of December 2021 at 16:10:48 then
{{now}} {{!- prints 2021-12-29T16:10:48Z --}} Dates can also be rendered in a specific timezone (the default is UTC): {{now timezone='Australia/Sydney' format='yyyy-MM-dd HH:mm:ssZ'}} {{!- prints 2021-12-30 03:10:48+1100 --}} Pass epoch as the format to render the date as UNIX epoch time (in milliseconds), or unix as the format to render the UNIX timestamp in seconds. {{now offset='2 years' format='epoch'}} {{!- prints 1703866248986 --}} {{now offset='2 years' format='unix'}} {{!- prints 1703866248 --}}
|
The date helper can be used to manipulate existing date values, changing the offset, timezone and print format in exactly the same manner as with the now helper. |
Given a myDate variable of say '2021-12-29' then the below expression will print '28/12/2021'.
{{date myDate offset='-1 days' timezone='EST' format='dd/MM/yyyy'}}
|
The parseDate helper enables you to parse a string into a date. You can also specify a format that the
date string is in. The parseDate helper is mostly used in conjunction with the date helper. The format can be specified as per Java date formats, with the addition of 'epoch' and 'unix'. |
For example you could parse a date from a HTTP header and then manipulate it with the date helper:{{date (parseDate request.headers.MyDate) offset='-1 days'}} And again, this time specifying the format that the MyDate is in: {{date (parseDate request.headers.MyDate format='dd/MM/yyyy') offset='-1 days'}} , which would print '2021-12-28T00:00:00Z' given a MyDate header of '29/12/2021'. The format can also be 'unix' if the value is in epoch seconds or 'epoch' if the value is in epoch milliseconds. e.g. {{date (parseDate '1703866248986' format='epoch')}}
|
Using the truncateDate helper you can truncate dates to say the first day of month etc. The available date truncations are: |
If the MyDate header is 'Tue, 15 Jun 2021 15:16:17 GMT' then the result of the following
expression would be '2021-06-01T00:00:00Z':{{date (truncateDate (parseDate request.headers.MyDate) 'first day of month')}} Similarly the below expression would produce an output of 'Wed Jun 30 00:00:00 UTC 2021': {{truncateDate (parseDate '2021-06-14T00:00:00Z') 'last day of month'}}
|
Random value
Description | Example(s) |
---|---|
The randomValue helper can be used to generate various random strings of a specific type and length.
Optionally, values containing alphabetic characters can be made upper case via the uppercase parameter. The types of random values supported are as follows: If no type is given then random ALPHANUMERIC characters will be generated and the default length is 36. |
{{randomValue length=33 type='ALPHANUMERIC'}}
|
The randomInt helper generates a typed whole number rather than a string which can be useful if you want to control lower and upper bounds or to use in further processing with the math or range helpers for instance. |
{{randomInt}}
|
The randomDecimal helper works similar to the randomInt helper and generate random decimal numbers with or without bounds. |
{{randomDecimal}}
|
The pickRandom helper can be used to pick a value from a literal list at random. | Pick '1', '2' or '3' at random:{{pickRandom '1' '2' '3'}} Or pick from a list passed as a parameter (assuming the 'names' attribute in the JSON is an array): {{pickRandom (jsonPath request.body '$.names')}}
|
Number, string & array
Description | Example(s) |
---|---|
The math helper performs common arithmetic operations. It can accept integers, decimals or strings
as its operands but will always yield a number as its output rather than a string. Addition, subtraction, multiplication, division and remainder (mod) are supported. |
{{math 1 '+' 3}}
|
The range helper can be used to produce an array of integers between the bounds (inclusive) specified. | Examples below generate integers between 3 and 8, and -2 and 2 respectively.
{{range 3 8}} The helper can also be combined with randomInt and each for instance to output random length, repeating pieces of content e.g.
{{#each (range 0 (randomInt lower=1 upper=10)) as |index|}}
|
The contains helper returns a boolean value indicating whether the string or array passed as the first parameter contains the string passed in the second. | It can be used as parameter to the if helper as per examples below.{{#if (contains 'abcde' 'abc')}}YES{{/if}} Or on it's own: {{#contains 'abcde' 'abc'}}YES{{/contains}}
|
The matches helper returns a boolean value indicating whether the string passed as the first parameter matches the regular expression passed in the second. |
Like the contains helper it can be used as parameter to the if helper:{{#if (matches '123' '[0-9]+')}}YES{{/if}} Or as a block element on its own: {{#matches '123' '[0-9]+'}}YES{{/matches}}
|
The regexExtract helper supports extraction of values matching a regular expression from a string. | A single value can be extracted like this:{{regexExtract request.body '[A-Z]+'}} Regex groups can be used to extract multiple parts into an object for later use (the last parameter is a variable name to which the object will be assigned): {{regexExtract request.body '([a-z]+)-([A-Z]+)-([0-9]+)' 'parts'}} Optionally, a default value can be specified for when there is no match: {{regexExtract 'abc' '[0-9]+' default='some default value'}}
|
The size helper returns the size of a string, list or map. | Example of getting the length of a string:{{size 'abcde'}} To find out how many 'things' HTTP request query parameters were passed you could do: {{size request.query.things}}
|
The trim helper can be used to remove whitespace from the start and end of the given input. | To trim the value of an HTTP header:{{trim request.headers.X-Padded-Header}} Trim the value between the trim tags: {{#trim}}
|
The array helper produces an array from the list of parameters specified. The values can be any valid type. Providing no parameters will result in an empty array. | Generate an array with three values:{{array 1 'two' true}} Generate an empty array: {{array}}
|
Encoding
Description | Example(s) |
---|---|
The base64 helper can be used to base64 encode and decode values. | Various example usages below:
{{base64 request.headers.X-Plain-Header}}
|
The urlEncode helper can be used to URL encode and decode values. | Various example usages below:
{{urlEncode request.headers.X-Plain-Header}}
|
Miscellaneous
Description | Example(s) |
---|---|
The hostname helper prints the local machine's hostname where EkkoProxy is running. | Print the hostname:{{hostname}}
|
The formData helper parses its input as an HTTP form, returning an object containing the individual fields as attributes. The helper takes the input string and variable name as its required parameters, with an optional urlDecode parameter indicating that values should be URL decoded or not. | The following example will parse the request body as a form, then output a single field formField3:{{formData request.body 'form' urlDecode=true}} If the form submitted has multiple values for a given field, these can be accessed by index:
{{formData request.body 'form' urlDecode=true}} , or
{{formData request.body 'form' urlDecode=true}}
|
Complex Mocking
If you need more complex mocking then you can take advantage of the mock engine's state machine using scenarios.
In the Mock Editor you can define a mock with a Scenario Name along with the Required Scenario State and New Scenario State - note the initial Required Scenario State
must be set to 'Started'. When that mock is invoked the state moves to the New Scenario State and mocks with the same Scenario Name which are having
that new state as the Required Scenario State will then respond when invoked. This allows you to create complex scenarios for one or more mocked services.
It's probably better explained with a few examples.
Imagine a show tip service (GET /tip) that returns a different tip every time it’s invoked. For that you would create several mocks for the same
endpoint Url but that has different Required Scenario State and New Scenario State along with different tip responses.
The first mock response could have a Scenario Name of 'ShowTip' with the Required Scenario State as 'Started' and say a New Scenario State
as 'SecondTip' as an example (it can be any string). The next mock with the same Url and Scenario Name would have the Required Scenario State and
New Scenario State as 'SecondTip' and 'ThirdTip' respectively and so forth. The last mock in the scenario would set the New Scenario State back to
'Started' thereby resetting the state machine for that mock scenario and the tips will start over. In the example given we would go through the
states as per below table.
Method | URL Value | Scenario Name | Required Scenario State | New Scenario State | Response |
---|---|---|---|---|---|
GET | /tip | ShowTip | Started | SecondTip | Tip of the day 1 |
GET | /tip | ShowTip | SecondTip | ThirdTip | Tip of the day 2 |
GET | /tip | ShowTip | ThirdTip | Started | Tip of the day 3 |
Another example would be a todo list where you have a service to get todo items (GET /todo/items) and another to add todo items (POST /todo/items). For this you could start of by creating two GET /todo/items mocks with a Scenario Name of say 'Todo List'. The first mock would have the Required Scenario State as 'Started' (with a response of one todo item) and the second mock a Required Scenario State of 'Second item added' (with a response of two todo items). Note you do not set any New Scenario State for these as that will be handled by the add todo item mock. Lastly you then create a mock for POST /todo/items which has the same Scenario Name, a Required Scenario State of 'Started' and a New Scenario State set to 'Second item added'. This means that when the POST /todo/items is invoked it will switch the state and the GET /todo/items will return the other response with the second item added on the todo list. The table below shows the different values needed to make the example work.
Method | URL Value | Scenario Name | Required Scenario State | New Scenario State | Response |
---|---|---|---|---|---|
GET | /todo/items | Todo List | Started | [ { "id": 1, "title": "delectus aut autem", "completed": false } ] |
|
GET | /todo/items | Todo List | Second item added | [ { "id": 1, "title": "delectus aut autem", "completed": false }, { "id": 2, "title": "quis ut nam facilis et officia qui", "completed": false } ] |
|
POST | /todo/items | Todo List | Started | Second item added |
These examples are relatively simple in order to explain the state machine concept, but once understood, does allow you to create some very realistic and complex scenarios.
Mocking Delays
To simulate calls over a network to an API, which can be delayed for many reasons like network congestion or excessive server load, EkkoProxy supports setting different types of delays on a per mock basis. Applications need to be resilient to these types of delays and they must be designed to cope with this inevitable variability, and tested to ensure they behave as expected when conditions aren't optimal.
You can use the settings detailed below to set a mock delay or if you need more control on when one or more delays should trigger then you can use the delay helper instead which gives you full programmatic control. See the Handlebars Helpers section for more details.
The below screenshot shows an example of a log-normal distributed delay.
The settings above would produce delays ranging in time (ms) as per below graph with a mean of 2166ms and mode of 1704ms.
The different delays are described below.
Delay Type | Description |
---|---|
Fixed | The fixed delay will hold up the response such that it will not be returned until after the specified number of milliseconds. |
Random (Log-normal) | This random delay type follows a log-normal distribution curve which allows for simulation of
more specific downstream latencies, such as a long tail. It is a pretty good approximation of
long tailed latencies centered on the 50th percentile. The input parameters to the delay function are:
|
Random (Uniform) |
This random delay follows a uniform distributed delay allowing for simulation of a stable latency
with a fixed amount of jitter. The input parameters to the delay function are:
|
Chunked Dribble | The chunked dribble delays sends the response body back in a number of chunks within a given period.
This is handy for simulating a slow network and testing deterministic timeouts. The input parameters to the delay function are:
|
Note, any delays set in the response body using the handlebars delay helper will execute before a delay set here.
Mocking Faults
In the real world APIs communicate over networks which can fail in ways that can destabilise your application. These scenarios can be hard to test with an actual web service, which is why it is beneficial to use web service mocks when testing to inject this faulty behaviour that is otherwise difficult to get the real service to produce on demand. With EkkoProxy you can set various fault types and specify how often they should occur.
You can use the settings detailed below to set a mock fault or if you need more control on when various faults should trigger then you can use the fault helper instead which gives you full programmatic control. See the Handlebars Helpers section for more details.
The below screenshot shows an example of returning the response of another mock with a 503 HTTP status code and a 50% chance of this occurring at the time the mock is invoked.
The following fault types can be selected:
Fault Type | Description |
---|---|
Empty Response | The fault returns a completely empty response which generally causes "socket hang up" or "empty response" error to be thrown by the client. |
Malformed Response | This fault sends an OK status header, then garbage and then closes the connection. Typically causes "invalid header token" parse error thrown by the client. |
Random Data | Sends garbage and then closes the connection. This generally causes "malformed http response" or "http parse error" thrown by the client. |
Reset Connection | Closes the connection, setting SO_LINGER to 0 and thus preventing the TIME_WAIT state being entered. This typically causes a "connection reset by peer" type error to be thrown by the client. |
Error Mock | With this fault you can configure another mock that will be used as a response depending on the
frequency setting. This would normally be a mock with an error HTTP response / status e.g. 500 but
it can be any other mock. This works recursively so if that other mock has a fault configured then this
would trigger according it's frequency and so forth. Note it's not recommended to configure a long chain of mocks in this way and you should not make any circular references. |
For the faults types listed above you can define a frequency for how often you want the fault to occur. The frequencies are:
Frequency | Description |
---|---|
Always | The configured fault will always occur when this mock is invoked. Note this option is not available when using the Error Mock fault type. |
N'th Time | The configured fault will trigger every N'th time the mock is invoked. |
Random | The configured fault will happen on a percentage probability basis at the time mock is invoked. For instance, if set to 50% then there is a 50/50 chance that the fault will occur every time the mock is called. |
Note, any faults set in the response body using the handlebars fault helper will execute before a fault set here.
JDBC mocking
JDBC ResultSet objects can be mocked with JSON responses by adding a mock for the particular JDBC request URL / command path. The command path is comprised of the JDBC object name followed by the method called on it e.g. /PreparedStatement/executeQuery. To setup a mock for this command path the proxy's mock path needs to be prefixed to it e.g. /sqlrequest/PreparedStatement/executeQuery assuming the mock path was /sqlrequest when you created the proxy. The JDBC commands are treated as HTTP POST requests with the SQL query as the request body and any SQL parameters as headers. The headers will have the SQL parameter index as key and the parameter value as the header value. This means you can setup mocks with different Body Patterns and/or Header matchers to respond with different results depending on the SQL query itself. The below screenshot shows a mock for the same.
ResultSet schema
To mock a ResultSet response you have to provide the response in JSON format following the below schema.
{ "type": "object", "title": "The JDBC resultset schema", "description": "Contains the column metadata along with the actual row data.", "required": [ "cols", "rows" ], "properties": { "cols": { "type": "array", "description": "The cols property contains the JDBC ResultSetMetaData information.", "items": { "type": "object", "description": "Contains the column metadata items.", "required": [ "catalogName", "className", "displaySize", "name", "precision", "scale", "schemaName", "tableName", "type", "typeName" ], "properties": { "catalogName": { "type": "string", "description": "The designated column's table's catalog name." }, "className": { "type": "string", "description": "The fully-qualified name of the Java class whose instances are manufactured if the method ResultSet.getObject is called to retrieve a value from the column." }, "displaySize": { "type": "integer", "description": "The designated column's normal maximum width in characters." }, "name": { "type": "string", "description": "The designated column's name." }, "precision": { "type": "integer", "description": "The designated column's specified column size." }, "scale": { "type": "integer", "description": "The designated column's number of digits to right of the decimal point." }, "schemaName": { "type": "string", "description": "The designated column's table's schema." }, "tableName": { "type": "string", "description": "The designated column's table name." }, "type": { "type": "integer", "description": "The designated column's SQL type." }, "typeName": { "type": "string", "description": "The designated column's database-specific type name." } } } }, "rows": { "type": "array", "description": "The actual row data as per columns described in the cols property.", "items": {} } } }
ResultSet example
The below shows a ResultSet JSON example response in full.
{ "cols": [ { "catalogName": "", "className": "java.lang.String", "displaySize": 15, "name": "GROUP_CODE", "precision": 15, "scale": 0, "schemaName": "", "tableName": "", "type": 12, "typeName": "VARCHAR2" }, { "catalogName": "", "className": "java.lang.String", "displaySize": 50, "name": "GROUP_CODE_DESCRIPTION", "precision": 50, "scale": 0, "schemaName": "", "tableName": "", "type": 12, "typeName": "VARCHAR2" }, { "catalogName": "", "className": "java.math.BigDecimal", "displaySize": 22, "name": "LOOKUP_ID", "precision": 10, "scale": 0, "schemaName": "", "tableName": "", "type": 2, "typeName": "NUMBER" }, { "catalogName": "", "className": "java.math.BigDecimal", "displaySize": 22, "name": "LOOKUP_TYPE", "precision": 6, "scale": 0, "schemaName": "", "tableName": "", "type": 2, "typeName": "NUMBER" }, { "catalogName": "", "className": "java.lang.String", "displaySize": 50, "name": "LOOKUP_VALUE", "precision": 50, "scale": 0, "schemaName": "", "tableName": "", "type": 12, "typeName": "VARCHAR2" } ], "rows": [ [ "FOO_BAR", "foo bar desc", 1401, 19203, "foo bar value" ], [ "BAR_FOO", "bar foo desc", 1395, null, "BAR_FOO_VALUE" ], [ "HUBBA", "Hubba bubba", 98372, null, "HUBBA_BUBBA Value" ], [ "Ekko Proxy", "Service virtualization", 59071, 199, "EKKO" ], [ "Tools", "Ekko tools", 75987, null, "The best" ] ] }