Facing challenges while using relative path and mapping test data from a json file to a request - karate

I am facing few issues while using relative path and mapping test data from a JSON file. I am having JSON POST request and a test data file in JSON format.
This is the test data I am using.
{
"name": "Test Data",
"description": "Information's mainly related with Users",
"testData": [
{
"Scenario1": {
"givenName": "Joseph",
"familyName": "George",
"addressType": "Current",
"lineOne": "BNRA-222, Kowdiar lane",
"cityName": "Trivandrum",
"countryID": "India",
"postcode": "695006"
}
},
{
"Scenario2": {
"givenName": "Sreenath",
"familyName": "Bhasi",
"addressType": "Current",
"lineOne": "HSE-123, Karyavatom",
"cityName": "Trivandrum",
"countryID": "India",
"postcode": "695552"
}
}
]
}
This is the feature file
Feature: Test using the Data from a JSON file
Background:
* def baseJsonRequest = read('../requests/jsonrequest.json')
* def baseData = read('../data/sampledata.json')
* def endPointURL = endPointURI + path
Scenario: A sample scenario to test the data parametrization
Given url endPointURL
And request baseJsonRequest
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.givenName = baseData.testData[*].Scenario1.givenName
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.familyName = baseData.testData[*].Scenario1.familyName
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.residenceAddress.addressType = baseData.testData[*].Scenario1.addressType
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.residenceAddress.lineOne = baseData.testData[*].Scenario1.lineOne
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.residenceAddress.cityName = baseData.testData[*].Scenario1.cityName
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.residenceAddress.countryID = baseData.testData[*].Scenario1.countryID
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson.residenceAddress.postcode = baseData.testData[*].Scenario1.postcode
My Questions are:
I am not able to give relative path on both sides. The relative path is returning me a json array. For eg I cannot use $..Scenario1.givenName, which makes me write longer paths.
To include this mapping on every scenario will be practically difficult. How can we implement a parameterized solution for that. What will better way? Can I invoke the data reading using a feature file and pass the informations to another feature. If that's possible then I need to parameterize . How to do that?
Or do I need to use a java class to read the JSON file?

Yes, the moment you have a wildcard in JsonPath, it returns an array. Anyway, 2 points that should help here straight away:
you can move repeating nested paths into a table-set
you can refer to a nested chunk of JSON by assigning to a variable
So this should be the way to go:
* def first = get[0] baseData.testData[*].Scenario1
* set baseJsonRequest.autoRequest.applicants.applicant.specifiedPerson
| path | value |
| familyName | first.familyName |
| residenceAddress.addressType | first.addressType |
I would try to not use wildcards as far as possible, for e.g.
* def first = $baseData.testData[0].Scenario1
Hope this helps !

Related

How to iterate over a dynamic array of objects and use each object as a parameter in test?

I started my adventure with Karate a month ago. I have a simple GET test called getAllCars.feature showing a list of cars currently available:
[
{
"brandName": "BMW",
"id": 1,
"winterTires": false,
"modelName": "X5"
},
{
"brandName": "Opel",
"id": 34,
"winterTires": true,
"modelName": "Insignia"
},
{
"brandName": "Mercedes-Benz",
"id": 36,
"winterTires": true,
"modelName": "GLE Coupe"
},
{
"brandName": "Huydai",
"id": 251,
"winterTires": false,
"modelName": "i30"
}
]
I have to use each id as a parameter for the next feature file, the problem is, the list of cars is dynamic, ids don't repeat and I will have to use this list of ids for several other feature files. I managed to create a helper getCarIds.feature, which creates an array of objects "carId": "#number":
Feature: Get car IDs
Scenario: Get car IDs
* call read('classpath:x/automation/cars/getAllCars.feature')
* def carIds = $response[*].id
* def carFeeder = karate.mapWithKey(carIds, 'carId')
The following getCarParameters.feature has to iterate over the array from getCarIds.feature and pass each id as a parameter to get a response with performance parameters of each car and I don't know how to use each id separately as a parameter (keeping in mind that the list of ids is changing):
Feature: Get parameters of each car
Scenario: Get parameters for each car
* call read('classpath:x/automation/cars/getCarIds.feature')
Given url carUrl + '/carparameters'
And param carId =
When method GET
Then status 200
I managed to do it when passing the values from getCarIds.feature to getCarParameters.feature like described here by adding following line to getCarIds.feature:
* call read('classpath:x/automation/cars/getCarParameters.feature') carFeeder
but several other tests require car ids. I need getCarIds.feature to be reusable, so I would have to retrieve data from feature file, which creates the array with ids, instead of passing it to the GET feature and apparently it isn't so easy. Maybe my approach is completely wrong.
I think this is a valid case for karate.callSingle(): https://github.com/karatelabs/karate#karatecallsingle
So you can actually stick this in any feature and it is guaranteed to execute only once across your test suite. If the data is indeed something used by a majority of your test suite, you could even do this initialization in karate-config.js.
So this should work. First the reusable feature common.feature. Instead of the hard-coded response, you know how to make an actual HTTP request.
#ignore
Feature:
Scenario:
* def response =
"""
[
{
"brandName": "BMW",
"id": 1,
"winterTires": false,
"modelName": "X5"
},
{
"brandName": "Opel",
"id": 34,
"winterTires": true,
"modelName": "Insignia"
}
]
"""
* print 'getting car ids'
* def carIds = response.map(x => ({ id: x.id }))
Note the use of the JS map() function above, which I have started to recommend instead of JsonPath.
And here is a feature that uses the above. This uses the new #setup annotation that makes it easy to "loop": https://github.com/karatelabs/karate#setup
You can try this example quickly, and watch it make 2 requests using a param id from the loop.
Feature:
#setup
Scenario:
* def data = karate.callSingle('call-single-common.feature').carIds
Scenario Outline:
* url 'https://httpbin.org/get'
* param id = id
* method get
Examples:
| karate.setup().data |
There are other ways to loop, refer: https://github.com/karatelabs/karate#data-driven-features

karate scenario outline - Create a dynamic example table based on JSON array index size

Here I would like to clarify about creating dynamic example table for a dynamic JSON index size
My JSON looks like
Env - Dev - 2 servers
"response": {
"abc": [{
"status": "pass"
.
.
},
{
"status": "pass"
.
.
}
]
}
Env - Uat - 3 servers
{
"response": {
"abc": [{
"status": "pass"
},
{
"status": "pass"
},
{
"status": "pass"
}
]
}
}
My scenario outline looks like
Scenario Outline: validating .....
When def result = callonce read('featurefilename#tagname')
Then print result
And print <status>
And print ...
And match ....
Examples:
|result.response.abc|
Errors for the above:
1) * dynamic expression evaluation failed:result.response.abc
2) com.intuit.karate.karateExpresion: ---- javascript evaluation failed result.response.abc, ReferenceError:"result" is not defined in at line number 1
Note - If I move step 'When def result = callonce read('featurefilename#tagname') to background it's working as expected but I can't use background in my feature file due to other factors.
Thanks in advance
Instead of providing index in a table you can leverage Dynamic Scenario Outline feature in karate.
In this case you you can pass the variable as a input to Examples. If the JSON provided above is from variable result then,
Examples:
| result.response.abc |
Refer the docs for more insights.

How to use templating in karate specific json schema

I am trying below in Karate.
I have a json schema ( for response validation) in .json file. There are few REGEXs that are common in many schemas. I want to extract them into one common file as key value pairs and use it across other schemas. Is it possible? if so, how can I do that? Is templating allowed in json schema?
Example:
Sample Json Schema File ( sample-response.json):
{
"response": {
"name": "#string",
"amount": "#regex ^(-?)([0]|[1-9][0-9]{0,15})[.][0-9]{2}$"
}
}
Feature File
Feature: Example feature
Background:
* def sampleResponse = read('./sample-response.json');
Scenario: Example scenario
When url 'https://someurl.com'
And method get
Then status 200
And match response == sampleResponse
What would I like to Do?
I would like to store the amount regex in json file as a reusable variable and use templating in json file to replace it.
Is it possible?
{
"response": {
"name": "#string",
"amount": "{{get regex from this template}}"
}
}
Yes. Embedded expressions work even when reading files.
So do this:
{
"response": {
"name": "#string",
"amount": "#(amount)"
}
}
And then do this:
Background:
* def amount = 100
* def sampleResponse = read('sample-response.json')
If you want the amount to come from another JSON file, why not, say this below is data.json:
{ "amount": 100 }
Then you do this:
Background:
* def data = read('data.json')
# you don't need the next line if you use "data.amount" as the embedded expression
* def amount = data.amount
* def sampleResponse = read('sample-response.json')

Karate : Dynamically input values from embedded expressions in a JSON file

* def mpRequestJson =
"""
{
"entity": '<entity>',
"consent": {
"PHONE": <updategetPhonePref>,
"EMAIL": true,
"POST": false,
"SMS": <updategetSMSPref>
},
"notices": [{
"title": "Privacy policy",
"version": "NA"
}],
"source": "web"
}
"""
Given path '<entity>'
And request mpRequestJson
When method PUT
Then status 200
And match $.consent.PHONE == '<updategetPhonePref>'
And match $.consent.SMS == '<updategetSMSPref>'
Examples:
|entity | updategetPhonePref|updategetSMSPref|
|xyz| #(updategetPhonePref)|#(updategetSMSPref)|
If i want to store the JSON request in a JSON file rather than in the feature file, what should be my JSON file?
In the JSON use embedded expressions, e.g.
entity: '#(entity)'
Then you can read it from a file:
* def mpRequestJson = read('my.json')
But before the read you should initialize variables that have to be substituted. So you will have some extra lines.
* def entity = '<entity>'
One way to reduce the extra lines is to create a temp JSON:
* def data = { entity: '<entity'>, phone: '<updategetPhonePref>' }
And then you can do this in the JSON:
entity: '#(data.entity)'
Read the docs on data driven tests also please.

Adding new key-value pair into json using karate

My payload looks like this :
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
There is an array dblist ["2","3"] , it would consists of numbers from 1 to 4. Based on the elements present in the list, I want to add key-values {"sku_price":"1500","sku_code":"2"} to my payload. I am using the following code :
* eval if(contains("3",dblist)) karate.set('pushRatesFromDS.sku_prices[]','{ "sku_price": "1500","sku_code":"3" }')
When I execute my feature file, I do not get any errors but, key-values are not added to my payload. However if I move this code to a new feature file and call it, key-value pairs get added to my payload. The code in my new feature file looks like : * set pushRatesFromDS.sku_prices[] = { "sku_price": "1500","sku_code":"2" }
Try this:
* def foo =
"""
{
"override_source": "DS",
"property_code": "0078099",
"stay_date": "2018-11-26T00:00:00.000000",
"sku_prices": [
],
"persistent_override": false
}
"""
* eval karate.set('foo', '$.sku_prices[]', { foo: 'bar' })