I have been working on Web automation projects. Generally, I prefer to use Cucumber due to providing behavior-driven development. Every project needs a different approach for execution. We need a runner class for executing feature files, but which test framework is better to use with Cucumber? I have three different options, two with TestNG and one with JUnit. You can prefer as your project needs. I’m listing from the simplest to the most complex ones.
Cucumber Runner with JUnit The first example is being created by Cucumber-JUnit and JUnit dependencies. I had been using it for an API project because I didn’t need TestNG annotation to perform it.
}
=============================================================================
2) Cucumber Runner with TestNG (AbstractTestNGCucumberTests) This example is being created by Cucumber-TestNG and TestNG dependencies. Using the advantages of TestNG, Test XML files can be created and feature files can be performed. We can execute more than one runner class by creating the XML file as concurrently.
3) Cucumber Runner with TestNG (IRetryAnalyzer) Sometimes one execution may not be enough for a test. IRetryAnalyzer provides repetitive execution. Now I’m working on Cucumber 6.11.0 and TestNG 7.5 versions. This runner class was created by using these versions. Please feel free to comment if you have any issues while applying your project! :)
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.
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.
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
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);
}
}
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.
Stack Memory in Java is used for static memory allocation and the execution of a thread.It contains primitive values that are specific to a method and references to objects referred from the method that are in a heap.
Access to this memory is in Last-In-First-Out (LIFO) order. Whenever we call a new method, a new block is created on top of the stack which contains values specific to that method, like primitive variables and references to objects.
When the method finishes execution, its corresponding stack frame is flushed, the flow goes back to the calling method, and space becomes available for the next method.
Key Features of Stack Memory
Some other features of stack memory include:
It grows and shrinks as new methods are called and returned, respectively.
Variables inside the stack exist only as long as the method that created them is running.
It’s automatically allocated and deallocated when the method finishes execution.
If this memory is full, Java throws java.lang.StackOverFlowError.
Access to this memory is fast when compared to heap memory.
This memory is threadsafe, as each thread operates in its own stack.
Heap Space in Java
Heap space is used for the dynamic memory allocation of Java objects and JRE classes at runtime. New objects are always created in heap space, and the references to these objects are stored in stack memory.
These objects have global access and we can access them from anywhere in the application.
We can break this memory model down into smaller parts, called generations, which are:
Young Generation – this is where all new objects are allocated and aged. A minor Garbage collection occurs when this fills up.
Old or Tenured Generation – this is where long surviving objects are stored. When objects are stored in the Young Generation, a threshold for the object’s age is set, and when that threshold is reached, the object is moved to the old generation.
Permanent Generation – this consists of JVM metadata for the runtime classes and application methods.
Key Features of Java Heap MemorSome other features of heap space include:
It’s accessed via complex memory management techniques that include the Young Generation, Old or Tenured Generation, and Permanent Generation.
If heap space is full, Java throws java.lang.OutOfMemoryError.
Access to this memory is comparatively slower than stack memory
This memory, in contrast to stack, isn’t automatically deallocated. It needs Garbage Collector to free up unused objects so as to keep the efficiency of the memory usage.
Unlike stack, a heap isn’t threadsafe and needs to be guarded by properly synchronizing the code.
How does it work:
Every time you create an instance of a class, some memory gets allocated on Heap to store that object and return a reference pointer to the start of that block of memory. This reference pointer comes in the form of a unique number represented in hexadecimal format, and as an integer it is stored on the Stack, so when we need to access that object on Heap, we find its reference on Stack which points to objects location on Heap, which is then accessed by that reference.
Example:
What is happening behind the wall(the scheme is showed below):
When JVM find main() method, the Stack frame will be created. After we create an instance of class Example, which means, that memory will be allocated on Heap to store the object and its address will be stored on Stack in form of a pointer.
When method fun1() is called, one more Stack frame will be created. local_var1 and its value will be stored on it, as it is local primitive variable.
Method fun2() is called. New Stack frame created and as with main() method memory allocation for object o will happen on Heap and pointer will be returned and saved on Stack.
Method fun3() is called. Parameter obj is saved on Stack as it is pointer to an object on Heap, local_var2 and its value is saved on Stack as well. Memory allocation for string will happen on the Heap in String Pool in Java.
After all GC will be invoked and memory will be released.