Skip to content
Branch: master
Find file History
ptrthomas added example related to #787
you CAN nest JS functions if you structure them via a re-usable feature
Latest commit 23526b6 Jul 8, 2019
Type Name Latest commit message Commit time
Failed to load latest commit information.
src important: eval now optional for 99% cases Jun 21, 2019 added example related to #787 Jul 8, 2019
karate-config.js path resolution for config for standalone jar #648 Jan 15, 2019
pom.xml bump version for develop branch Jul 5, 2019

Karate Netty

API Test-Doubles Made Simple.

And Consumer Driven Contracts made easy.


  • Everything on localhost or within your network, no need to worry about your data leaking into the cloud
  • Super-easy 'hard-coded' mocks (example)
  • Stateful mocks that can fully simulate CRUD for a micro-service (example)
  • Not only JSON but first-class support for XML, plain-text, binary, etc.
  • Easy HTTP request matching by path, method, headers, body etc.
  • Use the full power of JavaScript expressions for HTTP request matching
  • SSL / HTTPS with built-in self-signed certificate
  • Forward HTTP requests to other URL-s (URL re-writing)
  • Usable as a standard HTTP proxy server - simplifying configuration set-up for consuming applications
  • Start and stop mock servers in milliseconds
  • Super-fast HTTP response times (~20ms) for typical in-memory CRUD / JsonPath (as long as you don't do I/O)
  • Thread-safe - use concurrent consumers or async flows without fear
  • Zero errors even under load / stress - see this benchmark comparison with other tools
  • Easy integration into Java / JUnit test-suites via API
  • Server can dynamically choose free port
  • Think of it as a scriptable 'API gateway' or 'AOP for web-services' - insert custom functions before / after an HTTP request is handled
  • Just one file can script the above aspects, simplifying the mental-model you need to have for advanced scenarios such as Consumer Driven Contracts
  • Easily integrate messaging or async flows using Java-interop if required
  • Enables consumer or even UI dev teams to work in parallel as the provider service is being developed
  • Stand-alone executable JAR (25 MB) which only requires a JRE to run, ideal for web-developers or anyone who needs to quickly experiment with services.
  • Built-in CORS support for the ease of web-dev teams using the mock service
  • Option to use an existing certificate and private-key for server-side SSL - making it easier for UI dev / browser consumers in some situations
  • Configure a 'global' response header routine, ideal for browser consumers to add headers common for all responses - yet dynamic if needed
  • Provider service dev team can practice TDD using the mock + contract-test
  • The mock + contract-test serves as the ultimate form of documentation of the 'contract' including payload / schema details


Note that you can use this as a stand-alone JAR executable which means that you don't even need to compile Java or use an IDE. If you need to embed the mock-server into a JUnit test, you can easily do so.


The Netty based capabilities are included when you use karate-apache (or karate-jersey), so there is no extra dependency needed besides what is outlined in the main documentation.

Consumer-Provider Example

We use a simplified example of a Java 'consumer' which makes HTTP calls to a Payment Service (provider) where GET, POST, PUT and DELETE have been implemented. The 'provider' implements CRUD for the 'POJO', and the POST (or create) results in a message ( as JSON) being placed on a queue, which the consumer is listening to.

ActiveMQ is being used for the sake of mixing an asynchronous flow into this example, and with the help of some simple utilities, we are able to mix asynchronous messaging into a Karate test as well as the test-double.

A simpler stand-alone example (without ActiveMQ / messaging) is also available here: payment-service. You should be able to clone and run this project - and compare and contrast this with how other frameworks approach Consumer Driven Contract testing.

Key Source Code Description
C The 'consumer' or client application that consumes the demo 'Payment Service' and also listens to a queue
P The provider 'Payment Service'
1 An end-to-end integration test of the consumer that needs the real provider to be up and running
KC payment-service.feature A 'normal' Karate functional-test that tests the 'contract' of the Payment Service from the perspective of the consumer
2 JUnit runner for the above Karate 'contract' test, that depends on the real provider being up and running
KP payment-service-mock.feature A 'state-ful' mock (or stub) that fully implements the 'contract' ! Yes, really.
3 Uses the above 'stub' to run the Payment Service 'contract' test
4 Uses the 'fake' Payment Service 'stub' to run an integration test for the real consumer
KX payment-service-proxy.feature Karate can act as a proxy with 'gateway like' capabilities, you can choose to either stub a response or delegate to a remote provider, depending on the incoming request. Think of the 'X' as being able to transform the HTTP request and response payloads as they pass through (and before returning)
5a Here Karate is set up to act as an HTTP proxy, the advantage is that the consumer can use the 'real' provider URL, which simplifies configuration, provided that you can configure the consumer to use an HTTP proxy (ideally in a non-invasive fashion)
5b Karate acts as a URL 're-writing' proxy. Here the consumer 'knows' only about the proxy. In this mode (as well as the above 'HTTP proxy' mode which uses the same script file), you can choose to either stub a response - or even forward the incoming HTTP request onto any remote URL you choose.

Karate mocking a Queue has not been implemented for the last two flows (5) but can easily be derived from the other examples. So in (5) the Consumer is using the real queue.

Also see this blog post for an additional diagram explaining how a mock-service can be implemented.

Server-Side Karate

A perfect match !

It is worth calling out why Karate on the 'other side of the fence' (handling HTTP requests instead of making them) - turns out to be remarkably effective, yet simple.

  • 'Native' support for expressing JSON and XML payloads
  • Embedded Expressions are perfect for those parts of the payload that need to be dynamic, and JS functions can be 'in-lined' into the JSON or XML
  • Manipulate or even transform payloads
  • Validate payloads if needed, using a simpler alternative to JSON schema
  • Karate is all about making HTTP calls, giving you the flexibility to call 'downstream' services if needed
  • In-memory JSON and JsonPath solves for 'state' and filtering if needed
  • Mix custom JavaScript (or even Java code) if needed - for complex logic
  • Easily 'seed' data or switch environment / config on start
  • Read initial 'state' from a JSON file if needed

If you think about it, all the above are sufficient to implement any micro-service. Karate's DSL syntax is focused on exactly these aspects, thus opening up interesting possibilities. It may be hard to believe that you can spin-up a 'usable' micro-service in minutes with Karate - but do try it and see !

Standalone JAR

All of Karate (core, parallel / HTML reports, the UI and mocks) is available as a single, executable JAR file, which includes even the karate-apache dependency. This is ideal for handing off to UI / web-dev teams for example, who don't want to mess around with a Java IDE.

The only pre-requisite is the Java Runtime Environment. Note that the "lighter" JRE is sufficient, not the JDK / Java Development Kit. At least version 1.8.0_112 or greater is required, and there's a good chance you already have Java installed. Check by typing java -version on the command line.

Quick Start

It will take you only 2 minutes to see Karate's mock-server capabilities in action ! And you can run tests as well.

Tip: Rename the file to karate.jar to make the commands below easier to type !

  • Download the latest version of the JAR file from Bintray, and it will have the name: karate-<version>.jar
  • Download this file: cats-mock.feature (or copy the text) to a local file next to the above JAR file
  • In the same directory, start the mock server with the command:
    • java -jar karate.jar -m cats-mock.feature -p 8080
  • To see how this is capable of backing an HTML front-end, download this file: cats.html. Open it in a browser and you will be able to POST data. Browse to http://localhost:8080/cats - to see the saved data (state).
  • You can also run a "normal" Karate test using the stand-alone JAR. Download this file: cats-test.feature - and run the command (in a separate console / terminal):
    • java -jar karate.jar cats-test.feature
  • You will see HTML reports in the target/cucumber-html-reports directory

Another (possibly simpler) version of the above example is included in this demo project: karate-sikulix-demo - and you can skip the step of downloading the "sikulix" JAR. This project is quite handy if you need to demo Karate (tests, mocks and UI) to others !

Also try the "World's Smallest MicroService" !



You can view the command line help with the -h option:

java -jar karate.jar -h

Mock Server

To start a mock server, the 2 mandatory arguments are the path of the feature file 'mock' -m and the port -p

java -jar karate.jar -m my-mock.feature -p 8080


For SSL, use the -s flag. If you don't provide a certificate and key (see next section), it will automatically create cert.pem and key.pem in the current working directory, and the next time you re-start the mock server - these will be re-used. This is convenient for web / UI developers because you then need to set the certificate 'exception' only once in the browser.

java -jar karate.jar -m my-mock.feature -p 8443 -s

If you have a custom certificate and private-key (in PEM format) you can specify them, perhaps because these are your actual certificates or because they are trusted within your organization:

java -jar karate.jar -m my-mock.feature -p 8443 -c my-cert.crt -k my-key.key

Running Tests

Convenient to run standard Karate tests on the command-line without needing to mess around with Java or the IDE ! Great for demos or exploratory testing. Even HTML reports are generated !

Feature files (or search paths) to be tested don't need command-line flags or options and can be just listed at the end of the command.

java -jar karate.jar my-test.feature

Custom Classpath

Karate allows you to use custom Java code or 3rd party Java libraries using Java interop. Normally those who do this use Karate in the context of Maven or Gradle - and the classpath would be set automatically.

You can use the standalone JAR and still depend on external Java code - but you have to set the classpath for this to work. The entry-point for the Karate command-line app is Here is an example of using the 3rd-party SikuliX library, assuming it is in the current working directory.

java -cp karate.jar:sikulixapi.jar test.feature

If on Windows, note that the path-separator is ; instead of : as seen above for Mac / Linux. Refer this post for more details.

This approach is useful if you are trying to point the standalone Karate JAR file to a project structure that comes from the Java / Maven world. And the karate-config.js will be looked for in the classpath itself.

Scenario Name

If you only want to run a single Scenario by name, use the -n or --name option:

java -jar karate.jar -n "^some name$" my-test.feature

Note that you can run a single Scenario by line number - by appending it at the end of the feature name with a colon character. For Scenario Outline-s, you can even select a single Examples row by line-number.

java -jar karate.jar my-test.feature:42


You can specify Cucumber tags to include (or exclude) using the -t or --tags option as follows:

java -jar karate.jar -t @smoke,~@ignore my-test.feature


If your test depends on the karate.env environment 'switch', you can specify that using the -e (env) option:

java -jar karate.jar -e e2e my-test.feature


If karate-config.js exists in the current working directory, it will be used. You can specify a full path by setting the system property karate.config.dir. Note that this is an easy way to set a bunch of variables, just return a JSON with the keys and values you need.

java -jar -Dkarate.config.dir=parentdir/somedir karate.jar my-test.feature

Parallel Execution

If you provide a directory in which multiple feature files are present (even in sub-folders), they will be all run. You can even specify the number of threads to run in parallel using -T or --threads (not to be confused with -t for tags):

java -jar karate.jar -T 5 -t ~@ignore src/features

Output Directory

The output directory where the karate.log file, JUnit XML and Cucumber report JSON files would be output will default to target in the current working directory. The Cucumber HTML report would be found in a folder called cucumber-html-reports within this "output" folder. You can change the output folder using the -o or --output option:

java -jar karate.jar -T 5 -t ~@ignore -o /my/custom/dir src/features


The 'default' command actually brings up the Karate UI. So you can 'double-click' on the JAR or use this on the command-line:

java -jar karate.jar

You can also open an existing Karate test in the UI via the command-line:

java -jar karate.jar -u my-test.feature


A default logback configuration file (named logback-netty.xml) is present within the stand-alone JAR. If you need to customize logging, set the system property logback.configurationFile to point to your custom config:

java -jar -Dlogback.configurationFile=my-logback.xml karate.jar my-test.feature

Here is the 'out-of-the-box' default which you can customize. Note that the default creates a folder called target and within it, logs will be in karate.log.

<?xml version="1.0" encoding="UTF-8"?>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    <appender name="FILE" class="ch.qos.logback.core.FileAppender">
            <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    <logger name="" level="DEBUG"/>
    <root level="warn">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE" />


Starting and stopping a Karate server can be done via the Java API and this easily allows you to mix Karate into Java code, JUnit tests and Continuous Integration pipelines.

The class has a static start() method that takes 4 arguments:

  • file: a reference to the *.feature file you want to run as a server
  • port: int value of the port you want to use. 0 means, Karate will dynamically choose a free port (the value of which you can retrieve later)
  • ssl: boolean flag that if true, starts an HTTPS server and auto-generates a certificate if it doesn't find one, see SSL
  • arg: java.util.Map of key-value pairs that can be used to pass custom variables into the *.feature evaluation context - or null if not-applicable

There is an alternate start() method that takes references to the certificate and key if you want to use a custom certificate chain. Refer to the code for details:

The static start() method returns a FeatureServer object on which you can call a getPort() method to get the port on which the server was started.

And a FeatureServer instance has a stop() method that will stop the server.

You can look at this demo example for reference: - note how the dynamic port number can be retrieved and passed to other elements in your test set-up.

Within a Karate Test

Teams that are using the standalone JAR and don't want to use Java at all can directly start a mock from within a Karate test script using the karate.start() API. The argument can be a string or JSON. If a string, it is processed as the path to the mock feature file, and behaves like the read() function.

For more control, the argument to karate.start() can be a JSON with the following keys expected, only the mock is mandatory:

  • mock - (string) path to the mock feature file, e.g. classpath:my-mock.feature or relative paths work just like read().
  • port - (int) defaults to 0, see section on embedding above
  • ssl - (boolean) defaults to false, see above
  • cert - (string) see above
  • key - (string) see above
  • arg - (json) see above

So starting a mock from a Karate test is simple. This example also shows how conditional logic can be used effectively.

  * def port = karate.env == 'mock' ? karate.start('cats-mock.feature').port : 8080
  * url 'http://localhost:' + port + '/cats'

For the full example, look at cats-test.feature.

Server Life Cycle

Writing a mock can get complicated for real-life API interactions, and most other frameworks attempt to solve this using declarative approaches, such as expecting you to create a large, complicated JSON to model all requests and responses. You can think of Karate's approach as combining the best of both the worlds of declarative and imperative programming. Combined with the capability to maintain state in the form of JSON objects in memory, and Karate's native support for Json-Path, XML and embedded expressions - you have a very powerful toolkit at your disposal. And Karate's intelligent defaults keep things dead simple.

The Karate 'server' life-cycle is simple and has only 2 phases - the Background and Scenario. You can see that the existing Gherkin format has been 're-purposed' for HTTP request handling. This means that you get the benefit of IDE support and syntax coloring for your mocks.

Refer to this example: demo-mock.feature.

Also see how to stop a running server.


This is executed on start-up. You can read files and set-up common functions and 'global' state here. Note that unlike the life-cycle of 'normal' Karate, the Background is not executed before each Scenario.

Here's an example of setting up a function to generate primary keys which can be invoked like this: uuid()

Feature: stateful mock server

  * configure cors = true
  * def uuid = function(){ return java.util.UUID.randomUUID() + '' }
  * def cats = {}

Scenario: pathMatches('/cats') && methodIs('post')
    * def cat = request
    * def id = uuid()
    * = id
    * cats[id] = cat
    * def response = cat

Scenario: pathMatches('/cats')
    * def response = $cats.*

Scenario: pathMatches('/cats/{id}')
    * def response = cats[]

    def responseStatus = 404

The main Karate documentation explains things like the def, set and the eval keywords, Karate expressions and JsonPath.

The other parts of the simple example above are explained in the sections below.

Note that karate-config.js does not come into the picture here. But if for some reason you need to re-use an existing one, you can do this in the Background: * call read('classpath:karate-config.js') - and you can use any JS or JSON file in this manner to initialize a bunch of seed data or "intial state".


A server-side Feature file can have multiple Scenario sections in it. Each Scenario is expected to have a JavaScript expression as the content of the Scenario description which we will refer to as the "request matcher".

Note that the Scenario Outline is not supported when Karate is in "mock mode".

On each incoming HTTP request, the Scenario expressions are evaluated in order, starting from the first one within the Feature. If the expression evaluates to true, the body of the Scenario is evaluated and the HTTP response is returned.

It is good practice to have the last Scenario in the file with an empty description, (which will evaluate to true) so that it can act as a 'catch-all' and log or throw an error / 404 Not Found in response.

Request Handling

The Karate "server-side" has a set of "built-in" variables or helper-functions. They have been carefully designed to solve for matching and processing that you commonly need to do against the incoming HTTP request.

You can use these in the "request matcher" described above. This is how you can "route" incoming HTTP requests to the blocks of code within the individual Scenario-s. And you can also use them in the Scenario body, to process the request, URL, and maybe the headers, and then form the response.

The pathParams is a special case. For each request, it will be initialized only if, and after you have used pathMatches(). In other words you have to call pathMatches() first - typically in the "request matcher" and then you will be able to unpack URL parameters in the Scenario body.


This variable holds the value of the request body. It will be a JSON or XML object if it can be parsed as such. Else it would be a string.


Rarely used, unless you are expecting incoming binary content. This variable holds the value of the raw request bytes. Here is an example: _mock.feature.


Holds the value of the "base URL". This will be in the form http://somehost:8080 and will include the port number if needed. It may start with https if applicable.


Everything on the right side of the "base URL" (see above). This will include everything, including query string parameters if present. For example if the request URL was http://foo/bar?baz=ban the value of requestUri will be /bar?baz=ban.


The HTTP method, for e.g. GET. It will be in capital letters. Instead of doing things like: requestMethod == 'GET' - "best practice" is to use the methodIs() helper function for request matching.


Note that this will be a Map of List-s. For request matching, the typeContains(), acceptContains() or headerContains() helpers are what you would use most of the time.

If you really need to "route" to a Scenario based on a custom header value, you can use the karate.get() API - which will gracefully return null if the JsonPath does not exist. For example, the following would match a header of the form: val: foo

Scenario: pathMatches('/v1/headers') && karate.get('requestHeaders.val[0]') == 'foo'

Note that you can define your custom JS re-usable functions in the Background which can make complex matching logic easier to implement.


A map-like' object of all query-string parameters and the values will always be an array. The built-in convenience function paramValue() is what you would use most of the time.


Helper function that makes it easy to match a URI pattern as well as set path parameters up for extraction later using curly-braces. For example:

Scenario: pathMatches('/v1/cats/{id}')
    * def id =


JSON variable (not a function) allowing you to extract values by name. See pathMatches() above.


Helper function that you will use a lot along with pathMatches(). Lower-case is fine. For example:

Scenario: pathMatches('/v1/cats/{id}') && methodIs('get')
    * def response = cats[]


Function (not a variable) designed to make it easier to work with query parameters instead of requestParams. It will return a single (string) value (instead of an array) if the size of the parameter-list for that name is 1, which is what you need most of the time. For example:

Scenario: pathMatches('/greeting') && paramValue('name') != null
    * def content = 'Hello ' + paramValue('name') + '!'
    * def response = { id: '#(nextId())', content: '#(content)' }


Function to make matching the Content-Type header easier. And it uses a string "contains" match so that typeContains('xml') will match both text/xml or application/xml. Note how using JavaScript expressions makes all kinds of complex matching possible.

Scenario: pathMatches('/cats') && methodIs('post') && typeContains('xml')


Just like the above, to make matching the Accept header easier.

Scenario: pathMatches('/cats/{id}') && acceptContains('xml')
    * def cat = cats[]
    * def response = <cat><id>#(</id><name>#(</name></cat>


This should be sufficient to test that a particular header has a certain value, even though between the scenes it does a string "contains" check which can be convenient. If you really need an "exact" match, see requestHeaders.

For example, the following would match a header of the form: val: foo

Scenario: pathMatches('/v1/headers') && headerContains('val', 'foo')


A very powerful helper function that can run JsonPath or XPath expressions agains the request body or payload.

JSON example:

Scenario: pathMatches('/v1/body/json') && bodyPath('$.name') == 'Scooby'

XML example:

Scenario: pathMatches('/v1/body/xml') && bodyPath('/dog/name') == 'Scooby'

Refer to this example: server.feature.

Response Building

Shaping the HTTP response is very easy - you just set a bunch of variables. This is surprisingly effective, and gives you the flexibility to perform multiple steps as part of request processing. You don't need to build the whole response and "return" it on the last line. And the order of what you define does not matter.


The HTTP response code. This defaults to 200 for convenience, so you don't need to set it at all for "happy path" cases. Here's an example of conditionally setting a 404:

Scenario: pathMatches('/v1/cats/{id}') && methodIs('get')
    * def response = cats[]
    * def responseStatus = response ? 200 : 404


The actual response body or payload. Can be any Karate data-type such as JSON or XML.

Since you can use embedded-expressions, you can create dynamic responses with a minimum of effort:

Scenario: pathMatches('/v1/cats')
    * def responseStatus = 201
    * def response = { id: '#(uuid())', name: 'Billie' }

See the Background example for how the uuid function can be defined.


You can easily set multiple headers as JSON in one step as follows:

Scenario: pathMatches('/v1/test')
    * def responseHeaders = { 'Content-Type': 'application/octet-stream' }

configure responseHeaders

Many times you want a set of "common" headers to be returned for every end-point within a server-feature. You can use the Background section to set this up as follows:

    * configure responseHeaders = { 'Content-Type': 'application/json' }

configure cors

This allows a wide range of browsers or HTTP clients to make requests to a Karate server without running into CORS issues. And this is perfect for UI / Front-End teams who can even work off an HTML file on the file-system.

Like configure responseHeaders, this is also meant to be declared in the Background.

    * configure cors = true

This automatically adds the following headers to every response:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, PATCH


Use this to add an artificial delay instead of calling Thread.sleep() directly which will block all other threads. For example:

* def afterScenario = function(){ java.lang.Thread.sleep(3000) }

Refer to this example: payment-service-proxy.feature.

configure afterScenario

Just like the above, but you can set this "globally" for all route-handlers in the Background. Here is an example of setting a random delay between 200 to 600 milliseconds.

* configure afterScenario =
    var millis = 200 + Math.random() * 400;
    karate.log('sleeping for:', millis, 'millis')


Stop evaluating any more steps in the Scenario and return the response. Useful when combined with eval and conditional checks in JavaScript.

Scenario: pathMatches('/v1/abort')
    * def response = { success: true }
    * if (response.success) karate.abort()
    * print 'this will not be printed'

Proxy Mode


It is easy to set up a Karate server to "intercept" HTTP requests and then delegate them to a target server only if needed. Think of this as "AOP" for web services !

If you invoke the built in Karate function karate.proceed(url) - Karate will make an HTTP request to the URL using the current values of the request and requestHeaders. Since the request is mutable this gives rise to some very interesting possibilities. For example, you can modify the request or decide to return a response without calling a downstream service.

A twist here is that if the parameter is null Karate will use the host in the incoming HTTP request as the target URL - which is what you want when you run Karate as an HTTP proxy.

Refer to this example: payment-service-proxy.feature and also row (5) of the Consumer-Provider example

If not-null, the parameter has to be a URL that starts with http or https.

Karate cannot act as an HTTPS proxy yet (do consider contributing !). But most teams are able to configure the "consumer" application to use HTTP and if you set the target URL for e.g. like this: karate.proceed('') Karate will proxy the current request to the server. For example, you can set up Karate to log all requests and responses - which is great for troubleshooting complex service interactions.

After the execution of karate.proceed() completes, the values of response and responseHeaders would be ready for returning to the consumer. And you again have the option of mutating the response.

So you have control before and after the actual call, and you can modify the request or response - or introduce a time-delay using afterScenario.


A simple HTTP GET to /__admin/stop is sufficient to stop a running server gracefully. So you don't need to resort to killing the process, which can lead to issues especially on Windows - such as the port not being released.

If you have started the server programmatically via Java, you can keep a reference to the FeatureServer instance and call the stop() method. Here is an example:

Other Examples

The World's Smallest MicroService !

Which at 267 characters - is small enough to fit within a single tweet ! It implements a 'POST', 'GET by id' and 'GET all' for a /cats resource:


* def id = 0
* def m = {}

Scenario: methodIs('post')
* def c = request
* def id = ~~(id + 1)
* = id
* m[id + ''] = c
* def response = c

Scenario: pathMatches('/cats/{id}')
* def response = m[]

* def response = $m.*

To understand what the ~~ is doing, refer to the main Karate documentation on type conversion.

To get an idea of how much functionality the above code packs, have a look at the integration test for this service: cats.feature.

Want to try this out now ? It takes only 2 minutes.


The BenTen project is a great example of the usage of Karate test-doubles. This team was able to create a mock-service that simulates almost the entire life-cycle of an Atlassian JIRA ticket.

Here is the source code: benten-mock.feature. Note how complex JSON payloads have been separated out into files and elegantly loaded using the read function. State management just works and has been implemented in a few lines of extremely readable code.

You can’t perform that action at this time.