Benjamin Bischoff – Karate: Beyond API testing

Karate is famous for simplifying API testing, but this open-source tool offers much more than just checking backend endpoints. Built to reduce the barriers to writing tests and make maintenance easier, Karate is a powerful resource for test automation engineers. Peter Thomas, the creator of Karate, said that its goal is to reduce the friction needed to maintain a test, largely because of how readable the tests are. This makes it easier for everyone on the team to understand what the code is doing. We will explore how Karate goes beyond its core functionality, showing how it can handle complex UI testing, create data mocks, perform visual checks, and even run performance simulations.

9 min read
478 views

Karate uses a simple, readable syntax known as Gherkin. You may know Gherkin from Behavior-Driven Development (BDD). It uses the structure of GivenWhen, and Then:

  • Given: This sets up the test.
  • When: These are the actions you perform.
  • Then: This is where you check the results and run assertions.

Karate is unique because it removes the need for “glue code.” In other BDD frameworks, you need separate code to connect the English-language steps to the programming code. Karate has all the necessary code built into the framework.

Key Features of the Framework

The power of Karate comes from its fundamental design choices:

  1. Built-in Parallel Runs: If you run many tests, parallel execution is crucial. This feature is included in the basic framework; you do not need to add it later.
  2. Java and JavaScript Interoperability: You can extend the framework’s abilities by using Java or JavaScript code.
  3. JSON Focus: Karate handles data easily because everything is treated as JSON. If you work with other formats, like XML, Karate converts it internally to JSON so you can use the same assertion methods across all data types.

Running a Simple API Test

API tests are Karate’s core strength. A typical test scenario uses a feature file, which holds several test scenarios related to the same topic.

To run a test, you specify the URL endpoint using the url keyword. You can use a simple star (*) instead of the traditional Gherkin keywords (GivenWhen, etc.) to keep scenarios concise.

After setting the URL, you specify the HTTP method:

  • method get: To retrieve data.
  • method post: To send data.
  • method put: To update data.

Karate automatically stores the API response in a response variable and the status code in response status. You use the match keyword to assert that the status code is correct (for example, matching that the status is 200 for a successful request). This approach makes API test creation very straightforward and readable.

Configuring and Reusing Data

For complex testing, you often need to define settings that apply to multiple tests, such as a base URL for an application.

Using the Configuration File

In a standard Karate project, you use a file called karate-config.js. This file returns a JavaScript object that acts as your configuration.

For instance, you can define a base URL for your application:

// Example of what the config might hold
{
 baseUrl: 'http://your-app-url.com'
}

Any configuration parameter you define here becomes automatically available in all your test scenarios. This allows you to use variables like baseUrl directly in your feature files, ensuring consistency.

Capturing and Manipulating API Data

When you run an API test, the response data is available in the response variable. You can easily save this data into a custom variable for later use:

* def products = response

This saves the full response data into a variable named products.

Karate is built on JSON Path operations, allowing you to extract specific pieces of information. For example, if you have a list of products, you can easily pull out an array containing only the product names:

* def onlyTheNames = get products[*].name

This operation takes the products list, selects the name property from every element, and saves the results as an array of names. This ability to manipulate data is critical when you need to combine API data validation with UI testing.

Going Further with UI Testing

Karate is not limited to API calls; it can run UI-based tests in real browsers. This is similar to using tools like Playwright or Selenium. UI tests can either check that different visual components are working correctly, or they can test the data flow through the user interface (which is key for full end-to-end tests).

How UI Testing Works in Karate

To start a UI test, you first configure a web driver. Chrome is often used as the default driver for its flexibility.

You can specify the URL to open the website using the driver keyword:

* driver baseUrl

Once the browser is open, you can target web elements using various locators, including:

  • CSS selectors
  • IDs
  • Link text
  • XPath
  • Custom attributes (like data-test-id)

For better readability and maintenance, you can save complex locators into variables:

* def checkoutButton = locate('#data-test-checkout')

Performing UI Actions and Assertions

With the element located, you can perform actions like clicking:

* click checkoutButton

Or you can click directly on a locator:

* click '#data-test-id-cappuccino'

Karate’s power lies in matching and assertions, even on UI elements. You can check the text displayed on a button:

* match checkoutButton.text == 'Total: $19'

This checks the text property of the located element, ensuring that the displayed price is correct.

You can also include wait functions to ensure elements are visible before interacting with them:

* wait for '.modal-content'

Finally, you can take a screenshot at any point in the test. This is useful for visual verification or for automatically capturing the state of the application when a test fails.

Combining API and UI Tests for End-to-End Coverage

The most powerful feature for comprehensive testing is the ability to mix API calls and UI actions in a single scenario. This lets you confirm that the data pulled from the backend API is correctly displayed on the front end.

Handling Dynamic Data and Filters

Sometimes, the data displayed on the front end is modified or filtered from the raw API response. When testing, you must account for these differences to maintain deterministic test results.

In the case of testing a product list, if an API might send back discounted products, but you only want to test the regular product listings displayed on the site, you need to filter the API data first.

Here is where Java and JavaScript interoperability shines. You can define a function inside your test file to handle filtering:

// Defines a function to filter out items marked as 'discounted'
* def filteredProductNames = function(products) {
 return products.filter(function(product) {
 return !product.name.startsWith('discounted');
 }).map(function(product) {
 return product.name;
 });
}

By filtering the API products first, you know exactly what names should appear on the website.

Extracting Web Element Data

After filtering the API data, you open the browser and locate all the necessary UI elements (like all the product headlines on a page). You locate all elements at once:

* def productsWebElements = locateAll('h4')

Then, you need to extract the visible text from those web elements to compare it with the filtered API data. You can loop through the elements and use the karate.appendTo function to build an array of names shown on the screen:

// Create an empty array
* def productNamesWeb = []
// Loop through each web element
* for (var i = 0; i < productsWebElements.length; i++) {
 * karate.appendTo(productNamesWeb, productsWebElements[i].text.trim())
}

The key step is the final assertion. You match the array of names extracted from the website (productNamesWeb) against the array of filtered names from the API (filteredProductNames):

* match productNamesWeb == filteredProductNames

If the lists match exactly, your end-to-end data flow is working correctly.

Mocking APIs for Deterministic Testing

Testing against live APIs that frequently change or contain unpredictable data can lead to unreliable tests. Mocking solves this problem by allowing you to define your own API responses. When your test hits an endpoint, it receives data that you created, not the real data.

Defining a Mock Response

Karate has built-in mocking capabilities that use the same Gherkin syntax.

  1. Define the Mock Feature: You create a feature file defining your custom response data (e.g., a simplified JSON with only two specific products). You use the @ignore tag at the top so Karate knows this file is a mock definition, not a test to execute.
  2. Define Conditions (Scenarios): In the mock feature, a “scenario” acts as a filter. You define when the mock should be used. For example, if the path is /list.json and the method is get, then use your custom data.
  3. Define the Response: You use the response variable inside the mock scenario to specify the custom data to send back.

Using the Built-in Mock Server

Karate includes a mock server that runs locally:

  1. Start the Server: Use the karate.start method, pointing it to your mock feature file. This returns a specific port number where the mock server is running.
  2. Point the Application: You must point your application (or your test’s API calls) to localhost and the port number provided by the mock server.

This setup lets you test the mocked API itself and verify that the defined conditions and fallback responses work correctly.

Mocked API and UI Testing

The real power of mocking comes when you combine it with UI testing. When you test a real front-end application against a mock, you guarantee that the data source is predictable. This is essential for deterministic testing.

To use a mock with a real browser:

  1. Start Chrome: Open the browser driver with a blank page.
  2. Intercept Requests: Use the driver intercept function. You define a pattern (like requesting any JSON file) and instruct the driver to use your mock feature file instead of the real API endpoint.

* driver intercept 'pattern-for-json' 'path-to-mock-feature'

When the front-end application tries to load data, the driver intercepts the request and serves your custom mock data. You can then run complex UI tests, confirming that the front end displays the exact data you defined in your mock, even though the application is running live.

Visual Testing and Screenshot Comparison

Visual testing focuses on comparing screenshots rather than checking individual web element properties. This is often faster for large data tables or complex layouts, as it detects pixel shifts or layout problems.

Visual testing involves comparing a current screenshot against a baseline image. The baseline is an image saved beforehand, showing how the component or page should look.

How to Implement Visual Testing

  1. Define Viewport: Open the browser and set specific dimensions (width and height) to ensure the screenshots are comparable every time.
  2. Take a Screenshot: Capture the current state and save it to a variable.
  3. Compare Images: Use the compare image functionality. You specify the path to your baseline image and the variable holding the current screenshot.

If the images match, the test passes. If there are pixel differences (even a fraction of a percent), the test fails, and the report shows an overlay highlighting the areas that changed. This helps you quickly spot unintended layout changes or styling regressions.

Performance Testing with Minimal Code

Every test you write in Karate can also be used as a performance test. Karate includes a bridge to the powerful performance testing tool, Gatling.

Setting Up the Gatling Bridge

To run a performance simulation, you only need minimal additional configuration:

  1. Add Dependencies: If you use Maven, you include the Karate Gatling dependency and the Gatling Maven plugin in your project setup.
  2. Create a Simulation Class: You write a small Java class that extends the Gatling simulation class. This class tells Gatling which Karate feature file to run.
  3. Define Load: Inside the simulation class, you set the parameters, such as running 100 simulated users over a period of 10 seconds.

Once configured, you execute the test using a specific Maven command that triggers the Gatling profile. This runs your existing, simple Karate API test multiple times under load.

Analyzing Results

Gatling generates a detailed performance report. This report shows key metrics, including:

  • The number of requests executed.
  • The distribution of those requests over time.
  • The percentage of successful responses (status 200).

This quickly shows you how much load your API or application can handle before errors start appearing.

Conclusion

Karate is far more powerful than just an API testing tool. It provides a simple, unified framework for many types of testing that typically require separate tools.

The features we explored show that you can accomplish complex automation tasks with a minimal amount of code:

  • API Tests for checking backend functionality.
  • UI Tests for browser interaction and validation.
  • Combined Tests to ensure data flows correctly from API to UI.
  • API Mocks for creating reliable, deterministic test data.
  • Visual Testing for catching layout and pixel changes.
  • Performance Tests using the Gatling integration to check load capacity.
📋 Test management system for Automated tests
Manage automation testing along with manual testing in one workspace.
Follow us