Karate API Testing Tool Cheat Sheet

Karate is an opensource API testing tool developed by Peter Thomas from Intuit. Karate is built on top of HttpClient and Cucumber and has its own DSL to make API testing very easy. Although been around for almost a year, it has matured very quickly and has all the capabilities expected from an API testing tool.

Because Karate sits on top of cucumber, it inherits all the functionalities of cucumber, so you can write your API tests in simple Given When Then format and utilize all the cucumber keywords such as Feature, Scenario Outline, Scenario, Examples, Feature tagging.

I’ve created this cheat sheet to help anyone who is involved in testing APIs, giving examples of how to use the Karate tool.

Please note, this cheat sheet is just the tip of the iceberg. Karate has many other features that are not mentioned here. This list is just the most common operations typically used when testing APIs.

Add the dependencies (pom.xml)

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.plugin>3.7.0</maven.compiler.plugin>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <java.version>1.8</java.version>
    <karate.version>0.8.0.RC4</karate.version>
    <cucumber.reporting.version>3.13.0</cucumber.reporting.version>
</properties>

<dependencies>
    <dependency>
        <groupId>com.intuit.karate</groupId>
        <artifactId>karate-core</artifactId>
        <version>${karate.version}</version>
    </dependency>
    <dependency>
        <groupId>com.intuit.karate</groupId>
        <artifactId>karate-apache</artifactId>
        <version>${karate.version}</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.intuit.karate</groupId>
        <artifactId>karate-testng</artifactId>
        <version>${karate.version}</version>
    </dependency>
    <dependency>
        <groupId>net.masterthought</groupId>
        <artifactId>cucumber-reporting</artifactId>
        <version>${cucumber.reporting.version}</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Project Structure

You can organize and structure your maven project like this:

karate-config.js

This is where you can create variables which have a global scope. Karate reads this file before executing any scenario. This comes in very handy when switching environments which specific variables are used for different environments

function() {
    var env = karate.env; // get java system property 'karate.env'

    karate.log('karate.env selected environment was:', env);
    karate.configure("ssl", true)

    if (!env) {
    env = 'dev'; //env can be anything: dev, qa, staging, etc.
    }

    var config = {
    env: env,

    AM_USERNAME: 'devuser',
    AM_PASSWORD: 'devpass',
    AM_HOST: 'https://am.'+env+'.example.net',
    AM_AUTHENTICATE_PATH: '/am/json/realms/root/authenticate',

    IDM_USERNAME: 'devuser',
    IDM_PASSWORD: 'devpass',
    IDM_HOST: 'https://idm.'+env+'.example.net',
    IDM_MANAGED_USER_PATH: '/idm/managed/user',
    };

    if(env == 'qa') {
    config.AM_USERNAME: 'myUserName'
    config.AM_PASSWORD: 'myPa55word'
    }

    if(env == 'live') {
        config.AM_USERNAME: 'admin'
        config.AM_PASSWORD: 'secret'
    }

    karate.log('OpenAM Host:', config.AM_HOST);

    karate.configure('connectTimeout', 60000);
    karate.configure('readTimeout', 60000);

    return config;
}

How to send an HTTP Request (Get, Post, Put, Delete, Patch)

@FR
Feature: AM Admin Login

    Scenario: Login as Admin to AM and get token
    Given header X-OpenAM-Username = AM_USERNAME
    Given header X-OpenAM-Password = AM_PASSWORD
    Given url AM_HOST + AM_AUTHENTICATE_PATH
    And request ''
    When method POST
    Then status 200

    * assert response.tokenId != null
    * def tokenId = response.tokenId

In the above example, AM_USERNAME, AM_PASSWORD, AM_HOST, and AM_AUTHENTICATE_PATH come from the karate-config.js file.

’ can be interpreted as any of Given, When, Then, And, but when an action doesn’t serve a context, we can use ‘’.

’+’ acts as a concatenate operator

The above example sends an empty post body request. We can just use ‘ ‘

The method can be any valid HTTP verb (Get, Post, Put, Patch, Delete)

def’ is used to store a value in a variable.

header, url, request, method, status, response are all karate’s keywords forming the DSL. For the full list of keywords, visit Intuit.

In the above example, the response is JSON format, so we can use karate’s builtin JsonPath notation to parse the response.

Request Chaining with multiple API calls

Feature: request chaining with multiple api calls

Scenario: chain request demo

    * json req = read('classpath:com/example/templates/idm/create-user-template.json')
    * def user = req.givenName

    Given header X-Username = 'anonymous'
    Given header X-Password = 'anonymous'
    Given url AM_HOST + '/some/endpoint
    And request ''
    When method POST

    * def authId = response.authId

    * def payload1 =
    """
    {"authId":"${authId}","callbacks":[{"type":"NameCallback","output":[{"name":"prompt","value":"Email Address"}],"input":[{"name":"IDToken0","value":"${user}@putsbox.com"}]}]}
    """

    * replace payload1
    | token     | value  |
    | ${authId} | authId |
    | ${user}   | user   |

    * json mypayload1 = payload1

    Given header X-Username = 'anonymous'
    Given header X-Password = 'anonymous'
    Given url AM_HOST + '/openam/some-other-endpoint
    And request mypayload1
    When method POST

In the above example, the first call is made and the authId is parsed from the response and saved in a variable called authId. We then replace the second payload with the authId retrieved in the first call. We then use the new payload to send to the next API call.

How to read request templates and call other feature files

We can make our scenarios reusable and call them from other feature files. In this example, we can create a “generic” create-user.feature file where we can send the create user request but with a different request body

Feature: Create User in IDM

    Scenario: Create user in IDM with given guid

    Given header X-Requested-With = 'Swagger-UI'
    Given header X-OpenIDM-Username = IDM_USERNAME
    Given header X-OpenIDM-Password = IDM_PASSWORD
    Given url IDM_HOST + IDM_MANAGED_USER_PATH
    And request __arg
    When method POST
    Then status 201

Note, in the above example, we are using ‘__arg’ as the post body request.

We can then call the above feature file and pass in the required post body, which in turn we can read from a template

Feature: Create a user

    Scenario: Create user in IDM 

    * json myReq = read('classpath:com/example/templates/idm/idm-create-user-template.json')
    * call read('classpath:com/example/idm/idm-create-user.feature') myReq

The above code reads a template which is in location com/example/templates/idm/idm-create-user-template.json and stores it as a JSON variable called myReq

Then we can send the JSON variable to the other feature file using the call method.

The template looks like

{
    "mail" : "david@putsbox.com",
    "givenName" : "david",
    "sn" : "putsbox",
    "jobRole" : "developer",
    "telephoneNumber" : "91234567890",
    "dob" : "01/02/2010",
}

How to read other feature files - example 2

We can read a specific variable in the called feature file which is passed from a calling feature file

Feature: Create User in IDM

    Scenario: Create user in IDM with given guid

    Given header X-Requested-With = 'Swagger-UI'
    Given header X-OpenIDM-Username = IDM_USERNAME
    Given header X-OpenIDM-Password = IDM_PASSWORD
    Given url IDM_HOST + IDM_MANAGED_USER_PATH
    And request __arg.emailAddress
    When method POST
    Then status 201

Note, in the above example, we are using ‘__arg.emailAddress’ as the post body request. We are only interested in sending the email address as the request

We can then call the above feature file and pass in the required post body, which in turn we can read from a template

Feature: Create a user

    Scenario: Create user in IDM 

    * json myReq = read('classpath:com/example/templates/idm/idm-create-user-template.json')
    * json emailAddress = '{"emailAddress": "' +myReq.mail+ '"}'
    * call read('classpath:com/example/fr/idm/idm-create-user.feature') emailAddress

The above code extracts the mail field from the JSON template. When we pass a variable to another feature file, it must be of type JSON, so the variable emailAddress must be a valid JSON.

Then we can send the JSON variable to the other feature file using the call method and be sending the JSON variable, in this case, emailAddress.

Create a Test Runner class

We can execute the scenarios in the feature file using maven (which is useful to run the tests in a CI environment)

import com.intuit.karate.cucumber.CucumberRunner;
import com.intuit.karate.cucumber.KarateStats;
import cucumber.api.CucumberOptions;
import net.masterthought.cucumber.Configuration;
import net.masterthought.cucumber.ReportBuilder;
import org.apache.commons.io.FileUtils;
import org.testng.annotations.Test;

import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import static org.testng.AssertJUnit.assertTrue;

@CucumberOptions(tags = {"@FR", "~@ignore"})
public class TestRunner_FR {

    @Test
    public void testParallel() {
        String karateOutputPath = "target/cucumber-html-reports";
        KarateStats stats = CucumberRunner.parallel(getClass(), 1, karateOutputPath);
        generateReport(karateOutputPath);
        assertTrue("there are scenario failures", stats.getFailCount() == 0);
    }

    private static void generateReport(String karateOutputPath) {
        Collection jsonFiles = FileUtils.listFiles(new File(karateOutputPath), new String[] {"json"}, true);
        List jsonPaths = new ArrayList(jsonFiles.size());
        for (File file : jsonFiles) {
            jsonPaths.add(file.getAbsolutePath());
        }
        Configuration config = new Configuration(new File("target"), "YOUR PROJECT NAME");
        config.addClassifications("Environment", System.getProperty("karate.env"));
        ReportBuilder reportBuilder = new ReportBuilder(jsonPaths, config);
        reportBuilder.generateReports();
    }
}

The above code runs all the feature files which are tagged as “@FR” but ignores all the tests which are tagged as “@ignore”.

It also creates a cucumber report for visualizing the results of the test runs.

Run the tests from a command line or CI

mvn clean test -DargLine="-Dkarate.env=staging" -Dtest=TestRunner_FR

Here, we are running the TestRunner_FR class and setting the environment as staging.

Execute JavaScript in the Feature file

In a feature file, we have the capability to execute javascript as well

Feature: Generate a random session id

    Scenario: generate random session id
    * def random_string =
    """
    function(s) {
        var text = "";
        var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
        for (var i = 0; i < s; i++)
        text += possible.charAt(Math.floor(Math.random() * possible.length));
        return text;
    }
    """
    * def sessionId =  random_string(10)

The above code generates a random string of length 10 and saves it in a variable called sessionId.

Data Driven Tests

Since Karate sits on top of cucumber, data-driven testing comes as default

Feature: Data driven testing example

Scenario Outline: An 'Invalid input request' error is returned if required parameters have incorrect values.

    * def attribute_name = '<name_attribute>'
    * xml malformed_request = <method_call>
    * json activate_request = malformed_request

    * def activate_response = call read('activate.feature') activate_request

    * match activate_response.contentType == 'text/xml;charset=ISO-8859-1'
    * match activate_response.gas_version == '5.2.7'
    * match activate_response.error_code == '1000'

    Examples:
        | name_attribute  | method_call                                                                        |
        | auth_method     | Java.type('com.example.StringUtil').removeNodeByAttribute(xml_req, attribute_name) |
        | app_url         | Java.type('com.example.StringUtil').removeNodeByAttribute(xml_req, attribute_name) |

The example above utilizes Cucumber’s Scenario Outline and Examples keywords to create data-driven tests. To read each parameter, we use the angle brackets <>

Call Java from a feature file

package com.example;

public class StringUtil {

    public static String getNumberFromString(String text) {
        return text.replaceAll("\\D+", "");
    }
}
Feature: Call java demo

Scenario: Get number from text

    Given url 'https://preview.putsbox.com/p/david/last.json'
    When method GET
    * def emailText = response.text
    * def otpCode = Java.type('com.example.StringUtil').getNumberFromString(emailText)
    * print otpCode

The above feature file calls a Java method in the class called StringUtil. Then saves the response of that call to otpCode variable.