Skip to content

Karate JavaScript Mocks

Peter Thomas edited this page Dec 14, 2022 · 15 revisions

Karate 1.3.0 onwards supports an alternate way to write API mocks. The main difference is that mocks can be scripted in JavaScript instead of Gherkin. The main benefit is that complex conditional logic becomes easier to implement.

Karate's new mock system is designed to serve not just APIs but full-fledged dynamic server-side applications. Karate has a built-in HTML templating system based on Thymeleaf but with a simplified syntax using the embedded JS engine. It is designed to compose HTML chunks into complex applications. If you use a technology like HTMX you can render HTML chunks, perform partial DOM updates and get the user-experience of a SPA (Single Page Application). What this means is that you can create full-stack applications using Karate for internal use. You can find an example here.

The alternate way to write mocks will appeal to those who are familiar with JavaScript. The trade-off is that it becomes more complex and less-easy to read (for non-programmers) than the Gherkin syntax. The picture below will make the difference clear, and also explain some of the finer details of the Karate server-side JS API.

Both mocks support this rather complex API test that involves a "full cycle" of CRUD (Create, Read, Update and Delete): payments.feature

Here are links to the source files: payments-mock.feature | payments.js

Starting Mocks

Standalone JAR

Starting the standalone JAR with the --serve or -S option will use the current directory to serve files. As a convenience, the default "convention over configuration" is that API mocks are expected in a folder called /api/. And the URL would be http://localhost:8080/api/<resource-name>.js.

You can customize the following:

  • -p or --port / the default is 8080)
  • -w or --workdir / the default is .

If you pass a JavaScript file (with a .js extension) as the single -m or --mock command-line argument, that JS file will be used as the "controller". In this case all logic has to be in that one JS file and it will handle the "root" path or /. If you want to simulate a "context path" you can use the -P or --prefix CLI argument.

Also note that -s or --ssl can be used if needed.

Java API

The API is designed to be flexible and support controlling all the aspects you will need, such as HTTP session management. The Karate UI (Web Browser) automation regression suite uses the new mock-engine to serve both dynamic HTML and JS where needed. Here is an example. You use the HttpServer "builder" API to start a server using a config object.

To get a sense of all the customizable options, refer to the ServerConfig class.

JS API

request

request.<method>

Returns true if the <method> (lower-case) is the HTTP method of the incoming request. For example

if (request.post) {
  // do something
}

request.body

Get the body, if valid JSON, this will be a JS object ready to use for convenience.

request.param('<name>')

Gets the value of a query parameter by name and returns a string. This always returns a single (the first available) value, so if you are expecting multiple values with the same name in the request, use request.params['<name'].

Helper variants exist such as request.paramInt(), request.paramBool(), request.paramOr() (that takes a second argument to return a default value) and request.paramOrNull(). For HTML form-handling convenience, a param is considered "not present" not just when the name is missing from the request, but even if the value is an empty or blank string.

request.params

Returns all the request parameters as a multi-value map (JSON). So if you are expecting multiple values for a named param, you can do request.param['<name>'] which returns an array of strings.

request.pathMatches('<pattern>')

Returns true if the request path matches the pattern, for example:

if (request.pathMatches('/payments/{id}')) {
  let id = request.pathParams.id;
  // more processing
} 

Note how request.pathParams when called later will give you the values scraped out of the path.

Also see the image at the start of this page for an example.

request.pathParams

See request.pathMatches() above.

request.header('<name>')

Get a single header. Will always return a single value, so if you are expecting multi-value headers in the request, use request.headerValues('<name>'). The important thing here is that the header name is treated as case-insensitive.

request.headers

Return all the headers as a multi-value map (JSON).

To get a sense of all the useful things you can get out of a request, refer to the source: Request.

response

response.status

This defaults to 200.

response.body

You can set this to JSON or any other data type.

response.header('<name>', '<value>')

You can set a single header like so.

response.headers

You can set all the headers in one-shot as a JSON object.

To get a sense of all the useful things you can set on (or get out of) the response, refer to the source: Response.

session

This looks like a JSON object and you can add or remove things from it. You can customize the life cycle extensively. By default, it uses an in-memory store shared by all API calls for convenience.

The image at the start of this page shows example usage for a mock API. For an example in an HTML page, see here.

You can easily plug in your own implementation that uses a database or anything else. You can do this using the ServerConfig. The context object explained below has methods to init or destroy the session programmatically.

context

This contains a bunch of utility methods, including Karate style "match" operations. You can write some very complex API utilities and mix them into user-interfaces this way. You will probably use the read() function the most.

context.read()

Read a JS, JSON or text file. If JS, it will be evaluated. If you always want text, use context.readAsString()

context.uuid()

Helper to generate a UUID.

context.log()

Use this to log. Behaves just like the JS console.log() but ensures objects in the Karate JS engine are pretty printed.

context.match()

Brings the power of Karate's match into this JS syntax. The result would be as explained here.

var result = context.match(actualJson, expectedJson);
// if you want to specify the match type, you can use a string as the first argument
result = context.match('contains', actualJson, expectedJson);

context.http()

This is where things get really interesting. You can make an HTTP call to any end-point, and decide what to do with the response.

var res = context.http('https://jsonplaceholder.typicode.com/users').get().body;

To understand how to use the HTTP client "builder" returned by context.http(), refer to the source of HttpRequestBuilder.

To get a sense of all the other utility methods in the context object, refer to the source: ServerContext.

Advanced Example - Open API Documentation

For an advanced example of how the JavaScript option allows for a high-degree of dynamic logic, refer to this example project: karate-oas-demo. Note how the combination of mocks and tests leads to rich API documentation.

For more, you can refer to this presentation at the 2022 ASC (API Specifications Conference): Accelerate Adoption of OpenAPI with Test Automation. It includes a demo and explanation of what the above project does.