Automation QA Testing Course Content

Karate Framework

 Karate Framework:



In this article, we’ll get to know API Testing with Karate Framework and go over the sample project.

With the popularity of BDD (Behaviour Driven Development), using the gherkin style in the automation project makes sense because offering development that everyone can understand has many benefits and provides a quick process for the team. Let’s explain the benefits of this by comparing Karate with Rest Assured Framework.

Comparison of Karate with Rest-Assured Framework

Firstly I started to experience Rest-Assured Framework for it but after exploring the Karate Framework it happened to be my favorite one. Because building a structure is very important to create a project that is suitable for growth. Most of the time it’s a cost for the team to understand and build the project. Unlike Rest-Assured, The utilization of Karate has proven to be cost-effective. Karate already creates ready-to-use gherkin steps for almost every action with API. We can think of it as a collaborative project for each different API project.

I won’t go into much detail about Rest-Assured but if you want to compare them, I’ll be adding projects for the same API created with Karate and Rest-Assured.

After comparing these frameworks in general, Let’s look at how we can use the Karate framework.

Set up Environment

Karate Framework supports JDK 1.8 and higher, so I use JDK 11.

You can find below the pom.xml example. I used ‘maven-archetype-quickstart’ when I started my project. You can select this archetype from IDE.


The file has 3 dependencies, one of them is JUnit which I added to manage parallel execution for test cases, and others are used for Karate.

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>org.example</groupId>
  <artifactId>Grocery-Karate-API-Automation</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>
  <name>Grocery-Karate-API-Automation</name>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>11</java.version>
    <maven.compiler.version>3.8.1</maven.compiler.version>
    <maven.surefire.version>2.22.2</maven.surefire.version>
    <karate.version>1.2.0</karate.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-junit5</artifactId>
      <version>${karate.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>com.intuit.karate</groupId>
      <artifactId>karate-apache</artifactId>
      <version>0.9.6</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <build>
    <testResources>
      <testResource>
        <directory>src/test/java</directory>
      </testResource>
    </testResources>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>${maven.compiler.version}</version>
          <configuration>
            <encoding>UTF-8</encoding>
            <source>${java.version}</source>
            <target>${java.version}</target>
            <compilerArgument>-Werror</compilerArgument>
          </configuration>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>${maven.surefire.version}</version>
          <configuration>
            <argLine>-Dfile.encoding=UTF-8</argLine>
            <includes>
              <include>**/*TestRunner*.java</include>
            </includes>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>
As a second action, to manage environment configuration and the other ones such as base URL, API key, etc., we should create a karate-config.js file into the src/test/java path. Be sure this file is in the correct path otherwise, you can not use the configuration that into the karate-config.js
function fn() {
    var env = karate.env; // get system property 'karate.env'
    karate.log('karate.env system property was:', env);
    if (!env) {
        env = 'dev';
    }
    var config = {
        env: env,
        baseUrl: 'https://petstore.swagger.io'
    }
    if (env == 'dev') {
        // customize
        // e.g. config.foo = 'bar';
    } else if (env == 'e2e') {
        // customize
    }
    return config;
}

Now we’ve completed everything we need to start writing a script!

Let’s look at the examples with GET/POST/UPDATE/DELETE requests with Karate. After creating a sample feature file into the src/test/java path, you can write example requests like below.

There are the most basic GET and POST request examples. If you write a baseUrl, it reads from the karate config file automatically.

And also, you can verify the response with several types of match methods. (exact match, contains, present, etc.)

Feature: Grocery API

  Background: The Request Body Configuration
      # Set a configuration for the payload
    * url baseUrl

  Scenario: Get All Products from Grocery
    Given header Content-Type = 'application/json'
    And path '/allGrocery'
    When method get
    Then status 200
    And match response.data[*].id == '#present'
    And match response.data[*].price == '#present'
    And match response.data[*].name == '#present'
    And match response.data[*].stock == '#present'

  Scenario Outline: Get Grocery Details with a name
    Given header Content-Type = 'application/json'
    And path '/allGrocery/<name>'
    When method get
    Then status 200
    And match response.data[0].name == "<name>"
    And match response.data[0].id == '#present'
    And match response.data[0].price == '#present'
    And match response.data[0].stock == '#present'

    Examples:
      | name   |
      | apple  |
      | grapes |

  Scenario: Add a new product to the Grocery Basket
    * def jsonBody =
    """
{
    "id": 4,
    "name": "string",
    "price": 12.3,
    "stock": 3
}
 """
    Given header Content-Type = 'application/json'
    And path '/add'
    And request jsonBody
    When method post
    Then status 201
    And response.message == "success"

As you can see above, you can define the body payload in the scenario on the feature file and also you can create a JSON file and read it in the feature file.

On the other hand, Karate supports getting value from created java classes into the feature file. Compared with Rest Assured Framework, this feature makes the best difference in readability. You can find an example related to this.

{
  "id": 0,
  "category": {
    "id": 0,
    "name": "string"
  },
  "name": "hola",
  "photoUrls": [
    "string"
  ],
  "tags": [
    {
      "id": 0,
      "name": "string"
    }
  ],
  "status": "available"
}
pet.json
Feature: Create A new Pet

  Background: The Request Body Configuration
      # Set a configuration for the payload
    * url baseUrl
    * def requestPayload = read('classpath:payload/pet.json') #read the json file
    * set requestPayload.id = Java.type('utils.TestDataCreator').getID() #get value from java class
    * set requestPayload.category.id = Java.type('utils.TestDataCreator').getID() #get value from java class
    * set requestPayload.category.name = Java.type('utils.TestDataCreator').getDogCategoryName() #get value from java class
    * set requestPayload.name = Java.type('utils.TestDataCreator').getDogName() #get value from java class
    * set requestPayload.photoUrls[0] = Java.type('utils.TestDataCreator').getFileName() #get value from java class
    * set requestPayload.tags[0].name = requestPayload.name #get value from java class
    * set requestPayload.status = Java.type('utils.TestDataCreator').getStatus()[0] #get value from java class

  Scenario: Add a new pet to the store
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    And match response.id == '#present'
    And match $.name == requestPayload.name
    And match $.category.name == requestPayload.category.name
    And match $.status == requestPayload.status
    # Find the pet by ID
    Given header Content-Type = 'application/json'
    And path '/v2/pet/' + response.id
    When method get
    Then status 200
    And match $.id == requestPayload.id
    And match $.category.id == requestPayload.category.id
    And match $.category.name == requestPayload.category.name
    And match $.name == requestPayload.name
Example of the post request

Lastly, I’m adding the example of the delete and update requests.

Feature: Petstore 

  Background: The Request Body Configuration
    # Set a configuration for the payload
    * url baseUrl
    * def requestPayload = read('classpath:payload/pet.json')
    * set requestPayload.id = Java.type('utils.TestDataCreator').getID()
    * set requestPayload.category.id = Java.type('utils.TestDataCreator').getID()
    * set requestPayload.category.name = Java.type('utils.TestDataCreator').getDogCategoryName()
    * set requestPayload.name = Java.type('utils.TestDataCreator').getDogName()
    * set requestPayload.photoUrls[0] = Java.type('utils.TestDataCreator').getFileName()
    * set requestPayload.tags[0].name = requestPayload.name
    * set requestPayload.status = Java.type('utils.TestDataCreator').getStatus()[0]

  Scenario: Delete a pet
    # Create a new pet as the precondition
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    # Delete the pet from the store
    Given header Content-Type = 'application/json'
    And path '/v2/pet/' + requestPayload.id
    When method delete
    Then status 200
    And match $.code == 200

  Scenario: Updates the pet in the store
    # Create a new pet as the precondition
    Given header Content-Type = 'application/json'
    And path '/v2/pet'
    And request requestPayload
    When method post
    Then status 200
    # Create a new pet name & status for updating them.
    * def newName = Java.type('utils.TestDataCreator').getCatName()
    * def newStatus = Java.type('utils.TestDataCreator').getStatus()[2]
    # Send the update request
    Given header Content-Type = 'application/x-www-form-urlencoded'
    And  path '/v2/pet/' + requestPayload.id
    And form field name = newName
    And form field status = newStatus
    When method post
    Then status 200
    * print response
Example of the post request

Configuration Parallel Testing

Configuration parallel testing is quite easy. We only need the feature file path and JUnit that we added already into the pom.xml.

You can configure how many test cases should be executed simultaneously.

The parallel() method manages how many cases are running at the same time. I preferred 4 cases for my project but we can increase the amount. It doesn’t have any limit.

package runner;
import com.intuit.karate.Results;
import com.intuit.karate.junit5.Karate;
import static org.junit.Assert.*;
import org.junit.Test;


public class TestRunner {

   @Test
    public void testParallel() {
       Results results = Karate.run("src/test/java/features").parallel(4);
       assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
   }

}
TestRunner.java

Summary Report

Karate Framework generates the summary report automatically after running the test case. When all case execution is completed, the HTML report URL is created as follows.



Other Features and References

Karate Framework not only supports JSON as a format but also works on SOAP requests as XML. To give a wide assertion method structure, provide readable and ready-to-use request & response methods, and also retry configurations, this framework meets all the developer’s expectations.

I’m really glad to share my own experience with Karate. If you want also to use Karate for API testing, you can benefit from the main documentation from KarateLabs and also my example projects. Please feel free to ask me anything about it.

Finally, I’m pleased to be a part of the Insider and very excited about the work we will do in the future. If you would like to learn how we manage QA Process at Insider, you can read this article.


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.