Karate API Mocks
API Test-Doubles Made Simple.
And Consumer Driven Contracts made easy.
Index
Start | Standalone JAR | jbang | Downloading | Quick Start | Usage | Logging | The World's Smallest Microservice |
---|---|
Life Cycle | Java / JUnit | Within a Karate Test | Background | Scenario | Stopping |
Request | request | requestBytes | requestUrlBase | requestPath | requestUri | requestMethod | requestHeaders | requestParams | requestParts | pathMatches() | pathParams | methodIs() | paramExists() | paramValue() | typeContains() | acceptContains() | headerContains() | bodyPath() |
Response | response | responseStatus | responseHeaders | responseDelay | afterScenario | karate.abort() | responseStatus |
Advanced | configure cors | configure afterScenario | configure responseHeaders | Proxy Mode | karate.abort() | karate.proceed() | Consumer Driven Contracts | JavaScript Mocks |
Capabilities
- 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.
- Convert JSON or XML into dynamic responses with ease
- Maintain and read large payloads from the file-system if needed
- Mocks are plain-text files - easily collaborate within or across teams using Git / SCM
- 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
- Simulate slow, delayed, error or malformed responses with ease
- 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
- Support for hot-reload while editing a mock in development mode
- 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 (50 MB) which only requires a JRE to run, ideal for web-developers or anyone who needs to quickly experiment with services.
- Single-step install option via jbang - which even takes care of installing a Java runtime if required
- 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
Using
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.
Maven
The mock capabilities are a core part of Karate, and no separate library or dependency is needed.
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 Payment.java
‘POJO’, and the POST
(or create) results in a message (Shipment.java
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. Also refer to the documentation on handling async flows in Karate.
A simpler stand-alone example (without ActiveMQ / messaging) is also available here: examples/consumer-driven-contracts
. This is a stand-alone Maven project for convenience, and you just need to clone or download a ZIP of the Karate source code to get it. You can compare and contrast this example with how other frameworks approach Consumer Driven Contract testing.
Key | Source Code | Description |
---|---|---|
C | Consumer.java | The ‘consumer’ or client application that consumes the demo ‘Payment Service’ and also listens to a queue |
P | PaymentService.java | The provider ‘Payment Service’ |
1 | ConsumerIntegrationTest.java | 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 | PaymentServiceContractTest.java | 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 | PaymentServiceContractUsingMockTest.java | Uses the above ‘stub’ to run the Payment Service ‘contract’ test |
4 | ConsumerUsingMockTest.java | 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 | ConsumerUsingProxyHttpTest.java | 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 | ConsumerUsingProxyRewriteTest.java | 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.
This article by the creator of Karate is highly recommended as a reference: API Contract Testing - Visual Guide.
Read this answer on Stack Overflow if you want to know how Karate compares to Pact.
Also see this blog post for an additional diagram explaining how a mock-service can be implemented.
And for more ideas, refer to this example of using a mock to listen for an async “callback” and integrating that kind of flow into your test.
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 API testing, parallel-runner / HTML reports, mocks and web / UI automation) is available as a single, executable JAR file.
Downloading
The only pre-requisite (if not using jbang) is the Java Runtime Environment. Note that the “lighter” JRE is sufficient, not the full-blown JDK (Java Development Kit). At least Java 11 is required, and there’s a good chance you already have it installed. You can confirm this by typing java -version
on the command line.
Look for the latest release on GitHub and scroll down to find the “Assets”. Look for the file with the name: karate-<version>.jar
. Download it to the root of your project folder, and rename the file to karate.jar
to make commands easier to type.
Usage
Help
You can view the command line help with the -h
option:
java -jar karate.jar -h
Running Tests
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.
Here is how you can run a single feature file:
java -jar karate.jar my-test.feature
You can run all tests within a directory if you provide a directory path:
java -jar karate.jar some/folder
You can have multiple features (separated by spaces) or even folder paths as the last part of the command. Karate will run all feature files found in sub-directories.
For filtering tests to run, see the tags and scenario name options below.
Also see custom classpath and how to use a batch file for convenience.
Mock Server
To start a mock server, the 2 mandatory arguments are the path of the feature file ‘mocks’ -m
and the port -p
java -jar karate.jar -m my-mock.feature -m my-2nd-mock.feature -p 8080
Acting as an HTTP proxy server is not possible in 1.0 (it used to be possible in the past), and needs community contribution to revive.
SSL
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 -s -c my-cert.crt -k my-key.key
Hot Reload
You can hot-reload a mock feature file for changes by adding the -W or –watch option.
Note that if you are loading from the classpath:
your build system may need to update the file in the target
(or build
) folder when the source file changes. Or you could load the mock from the file-system using something like file:src/test/java/some/folder/my.feature
.
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
Tags
You can specify tags to include (or exclude) using the -t
or --tags
option as follows. Note that the special, built-in tag @ignore
is always skipped.
java -jar karate.jar -t @smoke,~@skipme my-test.feature
For an “AND” operation, repeat the CLI option:
java -jar karate.jar -t @one -t @two,@three my-test.feature
This has the effect of “one AND (two OR three)”
Dry Run
The option is -D
or --dryrun
to run tests in “dry run” mode.
karate.env
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
karate-config.js
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 -Dkarate.config.dir=parentdir/somedir -jar karate.jar my-test.feature
If you want to pass any custom or environment variables, make sure they are before the -jar
part else they will not be passed to the JVM. For example:
java -Dfoo=bar -Dbaz=ban -jar karate.jar my-test.feature
And now you can get the value of foo
from JavaScript or a Karate expression as follows:
var foo = karate.properties['foo']
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 @smoke src/features
Output Directory
The output directory where the karate.log
file and reports would be output - will default to target
in the current working directory. The HTML reports would be found in a folder called karate-reports
within this “output” folder. You can change the output folder using the -o
or --output
option:
java -jar karate.jar -T 5 -t ~@skipme -o /my/custom/dir src/features
Output Format
By default, the JUnit XML or Cucumber JSON report data will not be output. You can use the -f
or --format
option:
java -jar karate.jar -f junit:xml src/features
You can use comma-delimited values, for example: -f junit:xml,cucumber:json
.
To suppress the Karate HTML report output by default add ~html
.
Clean
The output directory will be deleted before the test runs if you use the -C
or --clean
option.
java -jar karate.jar -T 5 -C src/features
Debug Server
The -d
or --debug
option will start a debug server. See the Debug Server wiki for more details.
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 com.intuit.karate.Main
.
java -cp karate.jar:karate-robot.jar com.intuit.karate.Main 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.
Using A Batch File
When using the standalone JAR, you can create a batch file (or shell script) to make it easier to run tests. This is useful especially if you need to manage a custom classpath as desribed above.
Here is an example for Windows systems, name it as karate.bat
for convenience:
java -cp karate.jar;. com.intuit.karate.Main %*
Then you can just do karate my-test.feature
on the command-line. All options and arguments after karate
will be processed as explained in usage.
And here is an example for Linux / Mac systems, name it as karate
for convenience, and give it executable permissions by running (once): chmod +x karate
#!/bin/bash
java -cp "$(dirname "$0")/karate.jar":. com.intuit.karate.Main "$@"
Then you can just do ./karate my-test.feature
on the command-line. All options and arguments after ./karate
will be processed as explained in usage.
Both batch-file examples above add the current directory to the classpath (as .
), which is useful if you want to load a karate-config.js
file from the current directory. You can easily customize which java
executable is used, and the location of not just the Karate JAR, but any other JAR files containing even custom code.
jbang
Note that you can easily run Karate or even install applications based on Karate using jbang
. It will take care of setting up a local Java runtime, which is really convenient. Note that jbang itself is super-easy to install and there is even a “Zero Install” option.
With jbang installed, you can do this (since a jbang-catalog.json
is present within the karatelabs/jbang-catalog GitHub repository):
jbang karate@karatelabs -h
What’s really interesting is that you can install karate
as a local command-line application !
please replace
RELEASE
with the exact / version of Karate you intend to use if applicable
jbang app install --name karate com.intuit.karate:karate-core:RELEASE:all
And now the command karate
will be available in your terminal (after opening a new one or having re-loaded environment settings).
Which would make using Karate as easy as this !
karate -h
You can script complex automation, using the Java API that Karate makes available. So if you have a file called myscript.java
written as a jbang script, you can install it as a system-wide command called myscript
like this:
jbang app install --name myscript myscript.java
Refer to the jbang documentation for more options.
Logging
A default logback configuration file (named logback-fatjar.xml
) is present within the stand-alone JAR.
For convenience, if logback-test.xml
or logback.xml
exists on the root of the classpath (or the root of the working directory) - it will be used instead.
Another way to customize logging is 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"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.FileAppender">
<file>${karate.output.dir}/karate.log</file>
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="com.intuit.karate" level="DEBUG"/>
<root level="warn">
<appender-ref ref="STDOUT" />
<appender-ref ref="FILE" />
</root>
</configuration>
Embedding
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 com.intuit.karate.core.MockServer
class has static “builder” methods to configure and then launch a server. Here is an example.
You can call a getPort()
method on the MockServer
instance to get the port on which the server was started. Calling the stop()
method will stop the server.
You can look at this demo example for reference: ConsumerUsingMockTest.java - note how the dynamic port number can be retrieved and passed to other elements in your test set-up.
Continuous Integration
To include mocks into a test-suite that consists mostly of Karate tests, the easiest way is to use JUnit with the above approach, and ensure that the JUnit class is “included” in your test run. One way is to ensure that the JUnit “runner” follows the naming convention (*Test.java
) or you can explicity include the mock “runners” in your Maven setup.
You will also need to ensure that your mock feature is not picked up by the regular test-runners, and an @ignore
tag typically does the job.
For more details, refer to this answer on Stack Overflow.
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.
So starting a mock from a Karate test is simple. This example also shows how conditional logic can be used effectively.
Background:
* def port = karate.env == 'mock' ? karate.start('cats-mock.feature').port : 8080
* url 'http://localhost:' + port + '/cats'
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 likeread()
.port
- (int) defaults to0
, see section on embedding abovessl
- (boolean) defaults tofalse
, see abovecert
- (string) see abovekey
- (string) see abovearg
- (json) see abovepathPrefix
- (string) see above
So if you want to “hard-code” the port, you can do this:
* karate.start({ mock: 'cats-mock.feature', port: 9000 })
For the full example, look at cats-test.feature
.
The object returned by karate.start()
is an instance of MockServer
. So in addition to the port
“getter”, you can call stop()
on it, but you normally don’t need to - as it should stop automatically when the test (or JVM) ends.
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.
Background
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
Background:
* configure cors = true
* def uuid = function(){ return java.util.UUID.randomUUID() + '' }
* def cats = {}
Scenario: pathMatches('/cats') && methodIs('post')
* def cat = request
* def id = uuid()
* cat.id = id
* cats[id] = cat
* def response = cat
Scenario: pathMatches('/cats')
* def response = $cats.*
Scenario: pathMatches('/cats/{id}')
* def response = cats[pathParams.id]
Scenario:
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 theBackground
:* 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”.
Scenario
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 totrue
) 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 usedpathMatches()
. In other words you have to callpathMatches()
first - typically in the “request matcher” and then you will be able to unpack URL parameters in theScenario
body.
Scenario selection
When multiple files are provided, they are evaluated in supplied order
Example 1
: When a feature file contains same scenario: ```cucumber Scenario: pathMatches(‘/test’)- def response = read(‘/example/Bye.txt’)
Scenario: pathMatches(‘/test’)
-
def response = read(‘/example/hi.txt’) ``` Here the first scenario will be picked and Bye.txt will be returned as response.
Example 2
: When same scenario exists in two separate files.
Feature-file1.feature ```cucumber ##Feature-file1.feature Scenario: pathMatches(‘/test’)- def response = read(‘/example/Bye.txt’)
Feature-file2.feature
```cucumber
##Feature-file2.feature
Scenario: pathMatches('/test')
* def response = read('/example/Hi.txt')
The response will be determined by the order of file. java -jar karate.jar -m Feature-file1.feature -m Feature-file2.feature
Here Bye.txt
will be returned as response.
java -jar karate.jar -m Feature-file2.feature -m Feature-file1.feature
Here Hi.txt
will be returned as response.
request
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.
requestBytes
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
.
requestUrlBase
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.
requestPath
Only the path part, starting with /
. The query string will not be included. For example, if the request URL was http://foo/bar?baz=ban
- the value of requestPath
will be /bar
.
requestUri
Everything on the right side of the requestUrlBase
. This will include 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
.
requestMethod
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.
requestHeaders
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, use the karate.request
API. The advantage here is that it gets the value for a header ignoring-case. So here it doesn’t matter if the header key is Foo
or foo
:
Scenario: pathMatches('/v1/headers') && karate.request.header('foo') == 'bar'
For completeness, 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.
requestParams
A map-like’ object of all query-string parameters and the values will always be an array. The built-in convenience function paramExists()
is what you would use most of the time.
requestParts
This can be used to handle file-upload use-cases. If the incoming request is a multipart, this variable will be set and it is a Map
of List
-s. For example - if a file was in the request under the name myFile
, you can get the details like this:
Scenario: pathMatches('/v1/upload/excel')
* def filePart = requestParts['myFile'][0]
Each “part” (filePart
in the above example) will contain the following properties:
name
- the name (which will bemyFile
in the above example)charset
transferEncoding
filename
- the file name of this partcontentType
- the content-type of this partvalue
- the content as raw bytes
This is one of the rare places in Karate where raw bytes are exposed, but it should be easy for you to convert it into a string if needed.
* string message = filePart.value
Also refer to this article by Peter Quiel.
pathMatches()
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 = pathParams.id
The curly-braces can match only one “segment” of the path at any time. But you can pull off any kind of complicated match using plain old JavaScript and inspecting the other objects such as requestUri
. For example, to match any path that starts with /foo/
such as /foo/bar
and /foo/bar/baz
:
Scenario: requestUri.startsWith('foo/')
And since this is plain-old JavaScript you can even do this:
Scenario: !requestUri.startsWith('foo/')
pathParams
JSON variable (not a function) allowing you to extract values by name. See pathMatches()
above.
methodIs()
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[pathParams.id]
paramExists()
Function (not a variable) designed to match request on query parameter instead of requestParams
. Returns a boolean.
Scenario: pathMatches('/greeting') && paramExists('name')
paramValue()
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') && paramExists('name')
* def content = 'Hello ' + paramValue('name') + '!'
* def response = { id: '#(nextId())', content: '#(content)' }
typeContains()
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')
acceptContains()
Just like the above, to make matching the Accept
header easier.
Scenario: pathMatches('/cats/{id}') && acceptContains('xml')
* def cat = cats[pathParams.id]
* def response = <cat><id>#(cat.id)</id><name>#(cat.name)</name></cat>
headerContains()
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')
bodyPath()
A very powerful helper function that can run JsonPath or XPath expressions against the request body or payload.
JSON example:
Scenario: pathMatches('/v1/body/json') && bodyPath('$.name') == 'Scooby'
It is worth mentioning that because of Karate’s “native” support for JSON, you don’t need it most of the time as the below is equivalent to the above. You just use the request
object directly:
Scenario: pathMatches('/v1/body/json') && request.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.
response
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.
One of the great things about Karate is how easy it is to read JSON or XML from files. So when you have large complex responses, you can easily do this:
* def response = read('get-cats-response.json')
Note that embedded expressions work even for content loaded using read()
!
To give you some interesting ideas, say you had a program written in a different language (e.g. Python) and it happened to be invoke-able on the command line. And if it returns a JSON string on the console output - then using the karate.exec()
API you can actually do this:
* def response = karate.exec('some os command')
Because of Karate’s Java interop capabilities there is no limit to what you can do. Need to call a database and return data ? No problem ! Of course at this point you may need to stop and think if you need to use a real app server. But that said, Karate gives you a way to create full fledged micro-services in minutes - far faster than how you would using traditional technologies such as Tomcat, Node / Express, Flask / Django and the like.
responseStatus
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[pathParams.id]
* def responseStatus = response ? 200 : 404
responseHeaders
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:
Background:
* configure responseHeaders = { 'Content-Type': 'application/json' }
Note that Scenario
level responseHeaders
can over-ride anything set by the “global” configure responseHeaders
. This is convenient, as you may have a majority of end-points with the same Content-Type
, and only one or two exceptions such as text/html
or text/javascript
.
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
.
Background:
* configure cors = true
This automatically adds the following headers to every response:
Allow: GET, HEAD, POST, PUT, DELETE, PATCH
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, HEAD, POST, PUT, DELETE, PATCH
responseDelay
You can easily set response delay in milliseconds
Scenario: pathMatches('/v1/test')
* def responseDelay = 4000
Refer to this example: payment-service-proxy.feature
.
For more dynamic “global” behavior such as a random delay for every Scenario
, look at configure afterScenario
.
afterScenario
Use this to add re-use any behaviour after scenario run, e.g. logging. For example:
* def afterScenario =
"""
function(){
karate.log('finished')
}
"""
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.
Background:
* configure afterScenario = function(){ karate.set('responseDelay', 200 + Math.random() * 400) }
karate.abort()
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
karate.proceed()
It is easy to set up a Karate server to “intercept” HTTP and then delegate them to a target server only if needed. Think of this as “AOP” for web services !
(Intercepting HTTPS is not possible in 1.0, and needs community contribution to revive)
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
.
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
.
Stopping
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.
Tip: for stopping HTTPS servers, you can use curl like this:
curl -k https://localhost:8443/__admin/stop
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: ConsumerUsingMockTest.java.
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:
Feature:
Background:
* def id = 0
* def m = {}
Scenario: methodIs('post')
* def c = request
* def id = ~~(id + 1)
* c.id = id
* m[id + ''] = c
* def response = c
Scenario: pathMatches('/cats/{id}')
* def response = m[pathParams.id]
Scenario:
* 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.
BenTen
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.
JavaScript Mocks
Karate 1.3.0 onwards offers an option to write mocks in JavaScript which is suited for more complex server-side logic, validations or state-handling. Refer to the wiki for more.