Step-3: Reading data files, Scenario Outline, CSV files, Callers
Previously I talked about Karate fundamentals and simple usage of the API requests with Karate. This article will be about more advanced topics and include what you need while coding automated scripts and building your own test automation framework.
While you are writing your automation test scripts, you will need to use JSON models. But should you define all of the required models in the feature? That doesn't sound good, it is not the best practice. Karate has a feature that you can read JSON files directly in the feature file.
Reading JSON files
Reading JSON files or POJOs is the most complicated work on Java or similar programming languages. But Karate's features make it easy. One simple line of code will read your JSON object. Here is the implementation of the reading JSON file:
{
"name" : "foo",
"phone": 19872367759
}
JSON file under data package - modelFile.json
Scenario: Read JSON model from file
* def jsonModel = read('classpath:data/modelFile.json')
* print jsonModel
This code block will read the modelFile.json file under the data package and below is the output of the test scripts:
15:07:34.181 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] {
"name": "foo",
"phone": 19872367759
}
You have printed a JSON file to the console, but your primary task is not printing a JSON file to the console; you will use it for making API requests. Now, let's use the JSON file request body and create a new record on the API. I will use one of the public API, PetStore; you can refer to this document for more information.
{
"category": {
"id": 0,
"name": "categoryName"
},
"name": "petName",
"photoUrls": [
"someURL"
],
"tags": [
{
"id": 0,
"name": "tagName"
}
],
"status": "available"
}
JSON file under data package - petData.json
Scenario: Read JSON model from file and post it
Given url 'https://petstore.swagger.io/v2'
And path 'pet'
And def jsonBody = read('classpath:data/petData.json')
And request jsonBody
When method POST
Then status 200
Post request with JSON file
1 < 200
1 < Access-Control-Allow-Headers: Content-Type, api_key, Authorization
1 < Access-Control-Allow-Methods: GET, POST, DELETE, PUT
1 < Access-Control-Allow-Origin: *
1 < Connection: keep-alive
1 < Content-Type: application/json
1 < Date: Thu, 18 Mar 2021 13:30:08 GMT
1 < Server: Jetty(9.2.9.v20150224)
1 < Transfer-Encoding: chunked
{"id":9222968140497400482,"category":{"id":0,"name":"categoryName"},"name":"petName","photoUrls":["someURL"],"tags":[{"id":0,"name":"tagName"}],"status":"available"}
16:30:08.721 [pool-1-thread-1] INFO com.intuit.karate.Runner - <> feature 1 of 1:
Console output
As you see in the example above, Karate read the petData.json under the data package and added it to the request body then sent a post request with it. It produces a very detailed console output and you can easily see the result of your request.
HTML report: (paste into browser to view) | Karate version: 0.9.6
file:/Users/projectPath/target/surefire-reports/karate-summary.html
HTML report address
You can also find the HTML report in the console output and see the result by pasting it to the browser address bar. Or, you can navigate to the surefire-reports directory under the target folder and find the HTML report.
HTML report
Scenario Outline
While coding automation scripts in the BDD approach, you should use Scenario Outline structure to achieve Data-Driven testing. You should define your data under the examples table and use them in the scenario with their keyword. Let's create one scenario for scenario outline usage and print the data table to the console.
Scenario Outline: Data table printing <id>
* print 'current id is ->', id
Examples:
| id |
| 10 |
| 20 |
| 30 |
16:57:37.956 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] current id is -> 10
16:57:37.965 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - karate.env system property was:
16:57:37.967 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] current id is -> 20
16:57:37.972 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - karate.env system property was:
16:57:37.973 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] current id is -> 30
Console output of the Scenario Outline
In addition to the example above, you can use table reference names like <id>, ‘<id>’.
In the HTML report, you may want to see the id as a reference for execution. That's why I have used id in the scenario name to present the id value in the HTML report.
Now, let’s use this example table structure in a real API request. In the example below, a get request scenario takes parameters from the table and verifies the status.
Scenario Outline: GET request with example table - <id>
Given url 'https://petstore.swagger.io/v2'
And path 'pet', id
When method GET
Then status 200
Examples:
| id |
| 250 |
| 251 |
| 252 |
Reading Data Files
If you have UI automation experience with the BDD approach, you may know that it is impossible to read excel or CSV files directly through feature files. Guess what? It is possible with Karate! You can read the CSV files in feature files and use the data in your scenarios. Let's see how you can implement Data-Driven testing with CSV files through a feature file.
id
250
251
252
CSV file under data package - data.csv
Scenario Outline: GET request with CSV table - <id>
Given url 'https://petstore.swagger.io/v2'
And path 'pet', id
When method GET
Then status 200
Examples:
| read('data/data.csv')|
HTML report
This is incredibly convenient! You can handle Data-Driven testing with the read method through Scenario Outline.
Caller
I have shown you the most common cases you will need while coding automation test scripts with Karate. I want to mention one more topic that will save lots of time while creating your framework.
Here is the problem. There is a scenario that is responsible for one test case. And, one of the preconditions of the other scenario is the first scenario. How can you handle this problem? Do you write the first scenario inside of the second one? Well, that is not maintainable. Maybe you can think of putting both scenarios in one feature file and running these scenarios sequentially. But it will make your scenarios dependent on each other. So you need a structure that prevents code duplication, provides reusability, and makes your scenarios independent of each other. It is possible with the Callers structure in Karate.
Imagine that you have one test case, using one get request, and update the response with a new value. So we need to create a new feature to send a get request, use this request's response and send a put request. First, create a feature file for a get request:
Feature: get caller
Scenario: get scenario with static id
Given url baseUrl
And path 'pet', 250
When method GET
Then status 200
And print 'after get', response
callerGet.feature under callers package (baseUrl is petStore URL)
Now create a new feature that will call the feature above.
Scenario: caller feature usage
Given url baseUrl
And path 'pet'
And def getFeature = call read('classpath:callers/callerGet.feature')
And getFeature.response.name = 'newName'
And request getFeature.response
When method PUT
Then status 200
And print 'after put', response
Now, here is the output of the above feature:
10:53:59.941 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] after get {
"photoUrls": [
"string"],
"name": "doggie",
"id": 250,
"category": {
"name": "string",
"id": 0},
"tags": [{
"name": "string",
"id": 0}],
"status": "available"
}
10:54:00.552 [ForkJoinPool-1-worker-1] INFO com.intuit.karate - [print] after put {
"photoUrls": [
"string"],
"name": "newName",
"id": 250,
"category": {
"name": "string",
"id": 0},
"tags": [{
"name": "string",
"id": 0}],
"status": "available"
}
The executed Karate script above read the other feature file first and did all of the operations in this caller feature, then ran the remaining script in the current feature. Caller feature sends a get request to the API. The main feature uses the caller’s response, updates one value inside of the response, and sends a put request to update the record with new value.
It is an excellent structure. But if you notice, I have used static parameters, 250 as id in the get request. It is not a good way to use these callers. You should pass the parameters from one to another. Karate allows you to use parameters as well while calling other features. Let's use parameters and see how you can handle this problem.
Scenario: get scenario with parametric id
Given url baseUrl
And path 'pet', id
When method GET
Then status 200
And print 'after get', response
Scenario: caller
Given url baseUrl
And path 'pet'
And def getFeature = call read('classpath:callers/callerGet.feature'){id:251}
And getFeature.response.name = 'newName'
And request getFeature.response
When method PUT
Then status 200
And print 'after put', response
HTML report of caller scenario
I have modified the caller feature to use parameters as an id. Afterward, in the main feature, I called the get caller feature with an id and updated the caller’s response, and added this response as request body to send a put request.
You can pass whatever parameter you want with this approach, and you can use the parameters in the caller features as well. This approach is convenient; it will increase your framework reusability and also the quality of your code.
Conclusion
This article talked about some advanced features of Karate. If you have read through all three posts, you have learned most of the operations with the Karate Framework.
The following article will be about JUnit runners, Cucumber Reports, and command-line parameters.
Stay on the line and keep learning...
Resources: https://github.com/intuit/karate
Selcuk Temizsoy
Experienced Software Test Automation Engineer working in different industries. Software Test Consultant at kloia