Test automation with Karate II

Test automation with Karate II

Patricia Mateo Vega

QE Tester

March 22, 2022

What are we going to see?

Karate is an open-source testing framework that facilitates the development of automatic API tests in a fast, precise, and maintainable way, thanks to its simplicity and eloquence.

As we saw in the previous article, Karate has numerous features that can be very useful for its implementation.

We will be talking about three of them in this article.

Using Java classes

Karate provides the opportunity to use Java classes for complex utilities or helper functions that make it easy to debug and maintain. Thanks to this interoperability with Java, calls to databases can be included, which is a great help when it comes to verifying that the system's state is as expected.

Case of use

To put this functionality into practice, the get use case of the current date one year before will be used.

We will define a date utility class in Java code with a method that subtracts a year from a specific date as an example.

public class DateUtil {

   private static final Logger logger = LoggerFactory.getLogger(DateUtil.class);

   public static String getDate(){
       SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

       String dateString = format.format(new Date());
       logger.debug("date: {}", dateString);
       return dateString;
   }

   public static String getSubtractedYear(){
       DateTimeFormatter format = DateTimeFormatter.ofPattern("uuuu-MM-dd");
       LocalDate localDate = LocalDate.parse(getDate(), format);
       localDate = localDate.minusMonths(12);
       String dateString =  localDate.format(format);
       logger.debug("One year ago is: {}", dateString);
       return dateString;
   }
}

Now that we have created the Java class, we will make the feature that uses the call to the method.

Feature: timestamp Java example

  Background:
  * def dateUtil = Java.type('reqres_in_karate.utils.DateUtil')

  Scenario: Capture a year old date
     * def oneYearAgo = dateUtil.getSubtractedYear()
     * match oneYearAgo != null

It is effortless. It would suffice to define a variable with the Karate expression "Java.type" and the classpath and use it within the necessary scenario.

Inserting logs within the class, we can verify that the date info is correct:

10:22:08.580 [main] DEBUG reqres_in_karate.utils.DateUtil - Actual date: 2020-09-11
10:22:08.585 [main] DEBUG reqres_in_karate.utils.DateUtil - One year ago is: 2019-09-11

Environment configuration

Environment configuration provides support to change settings in different environments easily.

Usage example:

It is as simple as creating a karate-config-envName.js file for each environment. We will insert all the variables that we will need on it. In the runner, we will refer to the location of the file in the class that will carry out the operation:

package examples;

import com.intuit.karate.KarateOptions;
import com.intuit.karate.junit4.Karate;
import org.junit.BeforeClass;
import org.junit.runner.RunWith;

@RunWith(Karate.class)
@KarateOptions(features = "classpath:examples/timestampJavaENV.feature")
public class ExamplesRunnerEnv {
    @BeforeClass
    public static void beforeClass() {
        System.setProperty("karate.config.dir", "examples/environmentConfig");
    }
}

In the karate-config-envName.js file, we can define the environment's variables and java classes or other features that will be used.

function fn() {
 var env = karate.environment; // get system property 'karate.karate.environment'
 karate.log('env:', env);
 var config = { apiUrl: 'https://dev.reqres.in/api/users' };
 config.myJavaUtils = Java.type('demo_karate.utils.DateUtil');
 karate.log('config:', config);
 return config;
}

There would be two possible techniques when executing these tests on each environment. One would be to create different "runners." The other would be to use the annotation with the environment variable within the features. In that case, only the scenarios marked with the desired environment will be executed.

Feature: timestamp with config file

  Scenario: timestamp
     * def getDate = myJavaUtils.getDate()
     * print getDate
     * match getDate != null


  Scenario: Capture a year old date
  * def oneYearAgo = myJavaUtils.getSubtractedYear()
  * print oneYearAgo
  * match oneYearAgo != null

  @preprod
  Scenario: check environment preprod
    * match apiUrl == 'https://preprod.reqres.in/api/users'

  @dev
  Scenario: check environment dev
    * match apiUrl == 'https://dev.reqres.in/api/users'

We have to set a Java system property to change the environment. We can do this from the command line with the following instruction:

mvn test -Dkarate.options="--tags @dev" -DargLine="-Dkarate.env=dev" -Dtest=ExamplesRunnerEnv

If we execute the previous command, only three scenarios are launched within the feature for the example shown: the ones without annotation and the one that uses @dev. The @preprod would be ignored.

This type of solution makes the features shorter and more legible.

Parallel implementation

Karate offers the possibility of running the tests in parallel, drastically reducing the implementation time. This feature is specific to Karate. It does not depend on JUnit, Maven, or Gradle.

This type of execution allows you to easily select features or annotations to compose test suites in a very flexible way.

When executed, a "Results" object is generated to verify if any scenario failed, and it will also summarize the errors if there are any.

This functionality goes with the addition of both JUnit and Cucumber reports generation.

It can be introduced with both: JUnit 4 and JUnit 5. In this case, the example will be developed with the first one.

package examples.parallelRunner;

import com.intuit.karate.Results;
import com.intuit.karate.Runner;
import org.junit.Test;

import static org.junit.Assert.assertTrue;


public class ExamplesRunnerParallel {

   @Test
   public void testParallel() {
       System.setProperty("karate.env", "demo");
       Results results = Runner.path("classpath:examples/features").tags("~@ignore").parallel(5);                    assertTrue(results.getErrorMessages(), results.getFailCount() == 0);
   }

The Runner modifiers used are:

  • path: specifies the feature's location or set of features that you want to run. If there is more than one, they are separated by commas.
  • tags: to select the annotations. To omit one, it will suffice to place the operator "~" in front of the annotation that you do not want to run.
  • parallel: in this case, the number of threads you want to run in parallel is passed as a parameter.

To launch the tests, use the following command:

mvn test -Dtest=ExamplesRunnerParallel

Y como resultado se obtiene el siguiente log:

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 12.846 sec

Results :

Tests run: 1, Failures: 0, Errors: 0, Skipped: 0

In the example, the execution time is reduced almost by half compared with the sequential execution.

Code archive

You can find and download the code using this archive.

Conclusions

During this second part of the article, we have discussed new functionalities of Karate using examples with code. We have also verified that even though its simplicity is its main characteristic, versatility is essential. It helps to build a functional tests project that works with any API and can be built with minimal effort.

Patricia Mateo Vega

QE Tester