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

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

Related

How to loop new request according to response data list from previous step [duplicate]

I am using Karate version 0.8.0.1 and I want to perform following steps to test some responses.
I make a Get to web service 1
find the value for currencies from the response of web service 1 using jsonpath: $.currencies
Step 2 gives me following result: ["USD","HKD","SGD","INR","GBP"]
Now I use Get method for web service 2
From the response of web service 2 I want to get the value of price field with json-path something like below(passing the values from step 3 above):
$.holding[?(#.currency=='USD')].price
$.holding[?(#.currency=='HKD')].price
$.holding[?(#.currency=='SGD')].price
$.holding[?(#.currency=='INR')].price
$.holding[?(#.currency=='GBP')].price
So there are so many currencies but I want to verify price for only the currencies returned by web service 1(which will be always random) and pass it on to the the output of web service 2 to get the price.
Once i get the price I will match each price value with the value returned from DB.
I am not sure if there is any simple way in which I can pass the values returned by service 1 into the json-path of service 2 one by one and get the results required. Any suggestions for doing this will be helpful As this will be the case for most of the web services I will be automating.
There are multiple ways to do this in Karate. The below should give you a few pointers. Note how there is a magic variable _$ when you use match each. And since you can reference any other JSON in scope, you have some very powerful options.
* def expected = { HKD: 1, INR: 2, USD: 3}
* def response1 = ['USD', 'HKD', 'INR']
* def response2 = [{ currency: 'INR', price: 2 }, { currency: 'USD', price: 3 }, { currency: 'HKD', price: 1 }]
* match response2[*].currency contains only response1
* match each response2 contains { price: '#(expected[_$.currency])' }
You probably already have seen how you can call a second feature file in a loop which may be needed for your particular use case. One more piece of the puzzle may be this - it is very easy to transform any JSON array into the form Karate expects for calling a feature file in a loop:
* def response = ['USD', 'HKD', 'INR']
* def data = karate.map(response, function(x){ return { code: x } })
* match data == [{code: 'USD'}, {code: 'HKD'}, {code: 'INR'}]
EDIT - there is a short-cut to convert an array of primitives to an array of objects now: https://stackoverflow.com/a/58985917/143475
Also see this answer: https://stackoverflow.com/a/52845718/143475

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.

How to loop through karate response array and pass this in json path of another web service response

I am using Karate version 0.8.0.1 and I want to perform following steps to test some responses.
I make a Get to web service 1
find the value for currencies from the response of web service 1 using jsonpath: $.currencies
Step 2 gives me following result: ["USD","HKD","SGD","INR","GBP"]
Now I use Get method for web service 2
From the response of web service 2 I want to get the value of price field with json-path something like below(passing the values from step 3 above):
$.holding[?(#.currency=='USD')].price
$.holding[?(#.currency=='HKD')].price
$.holding[?(#.currency=='SGD')].price
$.holding[?(#.currency=='INR')].price
$.holding[?(#.currency=='GBP')].price
So there are so many currencies but I want to verify price for only the currencies returned by web service 1(which will be always random) and pass it on to the the output of web service 2 to get the price.
Once i get the price I will match each price value with the value returned from DB.
I am not sure if there is any simple way in which I can pass the values returned by service 1 into the json-path of service 2 one by one and get the results required. Any suggestions for doing this will be helpful As this will be the case for most of the web services I will be automating.
There are multiple ways to do this in Karate. The below should give you a few pointers. Note how there is a magic variable _$ when you use match each. And since you can reference any other JSON in scope, you have some very powerful options.
* def expected = { HKD: 1, INR: 2, USD: 3}
* def response1 = ['USD', 'HKD', 'INR']
* def response2 = [{ currency: 'INR', price: 2 }, { currency: 'USD', price: 3 }, { currency: 'HKD', price: 1 }]
* match response2[*].currency contains only response1
* match each response2 contains { price: '#(expected[_$.currency])' }
You probably already have seen how you can call a second feature file in a loop which may be needed for your particular use case. One more piece of the puzzle may be this - it is very easy to transform any JSON array into the form Karate expects for calling a feature file in a loop:
* def response = ['USD', 'HKD', 'INR']
* def data = karate.map(response, function(x){ return { code: x } })
* match data == [{code: 'USD'}, {code: 'HKD'}, {code: 'INR'}]
EDIT - there is a short-cut to convert an array of primitives to an array of objects now: https://stackoverflow.com/a/58985917/143475
Also see this answer: https://stackoverflow.com/a/52845718/143475

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

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 !

Is it possible to turn an array returned by the Mongo GeoNear command (using Ruby/Rails) into a Plucky object?

As a total newbie I have been trying to get the geoNear command working in my rails application and it appear to be working fine. The major annoyance for me is that it is returning an array with strings rather than keys which I can call on to pull out data.
Having dug around, I understand that MongoMapper uses Plucky to turn the the query resultant into a friendly object which can be handled easily but I haven't been able to find out how to transform the result of my geoNear query into a plucky object.
My questions are:
(a) Is it possible to turn this into a plucky object and how do i do that?
(b) If it is not possible how can I most simply and systematically extract each record and each field?
here is the query in my controller
#mult = 3963 * (3.14159265 / 180 ) # Scale to miles on earth
#results = #db.command( {'geoNear' => "places", 'near'=> #search.coordinates , 'distanceMultiplier' => #mult, 'spherical' => true})
Here is the object i'm getting back (with document content removed for simplicity)
{"ns"=>"myapp-development.places", "near"=>"1001110101110101100100110001100010100010000010111010", "results"=>[{"dis"=>0.04356444023196527, "obj"=>{"_id"=>BSON::ObjectId('4ee6a7d210a81f05fe000001'),...}}], "stats"=>{"time"=>0, "btreelocs"=>0, "nscanned"=>1, "objectsLoaded"=>1, "avgDistance"=>0.04356444023196527, "maxDistance"=>0.0006301239824196907}, "ok"=>1.0}
Help is much appreciated!!
Ok so lets say you store the results into a variable called places_near:
places_near = t.command( {'geoNear' => "places", 'near'=> [50,50] , 'distanceMultiplier' => 1, 'spherical' => true})
This command returns an hash that has a key (results) which maps to a list of results for the query. The returned document looks like this:
{
"ns": "test.places",
"near": "1100110000001111110000001111110000001111110000001111",
"results": [
{
"dis": 69.29646421910687,
"obj": {
"_id": ObjectId("4b8bd6b93b83c574d8760280"),
"y": [
1,
1
],
"category": "Coffee"
}
},
{
"dis": 69.29646421910687,
"obj": {
"_id": ObjectId("4b8bd6b03b83c574d876027f"),
"y": [
1,
1
]
}
}
],
"stats": {
"time": 0,
"btreelocs": 1,
"btreelocs": 1,
"nscanned": 2,
"nscanned": 2,
"objectsLoaded": 2,
"objectsLoaded": 2,
"avgDistance": 69.29646421910687
},
"ok": 1
}
To iterate over the responses just iterate as you would over any list in ruby:
places_near['results'].each do |result|
# do stuff with result object
end