Ok, so I am going to explain what my scenario is and how I have implemented it using karate. But i am looking for a better way of doing things which make my tests more readable and also want to use karate's api rather than too much javascript.
Scenario: I have a filter parameter for the api endpoint that i am testing and this filter param takes a json object of key-value pairs. So i have done the following:
Have created a filter-template.js as below:
function() {
var filter_params = {
jobId:null,
customerIds:[],
filterValues1:[],
filterValues2:[],
};
return filter_params;
}
I read this template in my karate scenario and because this is a template, at runtime i set the values in this template and run the test. I will have different values for key-value pairs so each test will set its own filter condition.
This is done by writing custom js function that takes the template as argument and also the filter condition values (referring to arg function argument below), sets the passed conditions to specific key's and then returns json object. Code below:
Scenario: Set filter scenario
* def filter_template = call read('filter-template.js')
* def filter_vals_list = [1001,1002]
* def filter_condition = { cnd1: 'foo', cnd2: '#(filter_vals_list)' }
* def setFilter =
"""
function(arg) {
var i;
var filter = arg.template;
filter.jobId = arg.condition.cnd1;
for(i=0;i<arg.condition.cnd2.length;i++)
{
filter.filterValues1.add(arg.condition.cnd2.get(i));
}
return filter;
}
"""
* def getFilter = call setFilter { template: '#(filter_template)',
condition: '#(filter_condition)' }
I then pass the getFilter as a param to my api request.
What I am hoping to understand is:
How can i get away from using JS loops above when setting filter?
Use karate's in-built functions like karate.map(), karate.forEach() to simplify the tests.
Any better approach if possible on tackling this scenario.
Help and guidance much appreciated.
From what I understood, I have simplified your scenario below:
* def filter_vals_list = [ 1001, 1002 ]
* def job_id = 'foo'
* def filter_template =
"""
{
jobId: '#(job_id)',
customerIds: [],
filterValues1: '#(filter_vals_list)',
filterValues2: [],
}
"""
Let me know if I missed anything. Please refer embedded expressions: https://github.com/intuit/karate#embedded-expressions
Now, you can easily use a re-usable JSON for the template, by changing the last step to the below, and yes - embedded expressions work even in re-usable JSON files !
* def filter_template = read('filter-template.json')
You may get even better ideas once you try the data-driven Scenario Outline. So hope that makes it clear how you are un-necessarily complicating things with JS ! You don't even need karate.map() etc.
Related
I have multiple scenarios in a feature file. At the end of each scenario, I need to "clean up" before the start of the next scenario. My clean up function requires a json object to be passed in. Each scenario has a different object. Therefore, I need to use embedded expression, so dynamic data gets erased.
My setup is like this:
* configure afterScenario =
"""
function(){
var deleteData = { customerData: '#(portfolio)' };
karate.call(deleteData.feature#deletePortfolio', deleteData);
}
And scenario may look something like this:
// here we get a brand new, unused "portfolio" value from a related function.
* table customer1
|portfolio | status |
|portfolio | 200 |
* call read(random.feature#random) customer1
So at the end of the above scenario, I expect the afterScenario to kick in and delete the "portfolio" variable value for that scenario. However, because it's a Java interop inside the afterScenario block, it doesn't recognize Karate's embedded expressions. Any way around this?
The moment you are within a JS block, you are "out" of Karate. So embedded expressions don't work, but "JS style" expressions work.
Read this once to be more clear about this: https://github.com/karatelabs/karate#karate-expressions
So this will work:
* configure afterScenario =
"""
function(){
var deleteData = { customerData: portfolio };
karate.call('deleteData.feature#deletePortfolio', deleteData);
}
Or even:
* configure afterScenario = function(){ karate.call('deleteData.feature#deletePortfolio', { customerData: portfolio }) }
One more tip, karate.get('someVariableName') can get you the value of any variable, any time.
And I do think you are over-engineering your tests, please don't:
https://stackoverflow.com/a/46080568/143475
https://stackoverflow.com/a/60944060/143475
https://stackoverflow.com/a/54126724/143475
The below function in feature files was working for version 0.9.2. Upgraded to 0.9.3 and this gives error : javascript function call failed: Index: 0.0, Size: 0. Code below:
var cnd = ['test1','test2'];
function set_filter(arg)
{
var i;
var filter = {filterValues:[]};
for(i=0;i<arg.length;i++)
{
filter.filterValues[i] = arg[i];
}
return filter;
}
set_filter(cnd)
Also i was earlier able to push values in a javascript array using below, but this has also stopped working in 0.9.3. Get error:javascript function call failed: TypeError: arr.push is not a function
var arr = [];
arr.push('test1','test2');
Try the scenario below that works in 0.9.2 but reports error (mentioned above) in 0.9.3
Scenario: JS test
* def filter_template =
"""
function() {
var filter_params = {
filterValues:[]
};
return filter_params;
}
"""
* def template = call filter_template
* def filter_condition = ['test1','test2']
* def setFilter =
"""
function(arg) {
var i;
var filter = arg.filter_template;
for(i=0;i<arg.condition.length;i++)
{
filter.filterValues[i] = arg.condition[i];
}
return filter;
}
"""
* def getFilter = call setFilter { filter_template: '#(template)', condition: '#(filter_condition)' }
* print getFilter
Help is much appreciated.
We've made the JS conversions stricter, everything will be a Java collection behind the scenes. If you make this change, things will start working:
filter.filterValues.set(i, arg.condition.get(i));
The same goes for push() - use add() or karate.appendTo(varname, value) instead.
My strong recommendation is don't use too much of JS logic especially loops. Karate has functions such as map(), forEach() and repeat() to solve for these needs. Please refer the docs here: https://github.com/intuit/karate#loops
You will thank me later because it will make your scripts easier to understand and maintain. One reason why this is needed is to pave the way for us to change the JS engine in the future.
In feature file i am writing assertion as
match response contains {token_type: '#string' }
Is there a way to get the output status of the above assertion value like true or false,
so that i can extend that into java for further processing?
The whole point of Karate is to avoid Java, and I don't recommend doing anything else.
But since you insist, if you want to get the equivalent of the above, just do something like this. Yes, you have to use "normal" JS fundas.
* def response = { token_type: 'foo' }
* def isString = typeof(response.token_type) === "string"
* match isString == true
For example i do
* def fooresponse = call read('../getfooid.feature')
* def jsfunction= """(fooresponse){
console.log(fooresponse)}"""
is that possible? what is the recommended way to do it?
Thanks!
edit: fixing js syntax lol
Yes, this will work if the function has been defined after the variable fooresponse:
* def jsfunction = function(){ karate.log(fooresponse) }
Yes, you can pass a single argument to karate.call().
Keep in mind that the #(foo) substitution does NOT apply to pure-JS, explained here: https://github.com/intuit/karate#karate-expressions
I think once you read the above link AND if you are familiar with JS, you will know what to do.
Like this:
* def fun = function(){ karate.call('foo.feature', { bar: 'baz' }) }
Suppose I have saved followings functions in a Utility js file.
function getCurrentDate(){
return 'date';
}
function getMonth(){
return 'Oct';
}
Please help me how any of these methods can be accessed in feature file.
I tried following code but it is not working.
* def fun = call read('Utility.js')
* def result = getData()
or
* def result = fun.getData()
In Karate, a JS file can contain only one function and it does not need a name, take a closer look at the examples.
I don't really recommend combining multiple functions into one file, it just makes things much harder to maintain. But if you really insist, here's how:
function() {
return {
getCurrentDate: function(){ return 'date' },
getMonth: function(){ return 'month' }
}
}
EDIT: a much better answer is here: https://stackoverflow.com/a/49384760/143475
* def data = karate.read('classpath:<path>/getRandomString.js')(<inputToTheFunctionIfAny>);
The above code works.