Configurable option for generating body content in Post requests
Hi,
Our team works on testing a REST API. It is set up by use of a Yaml file (Swagger).
We use certain Post requests (verbs) in a lot of testcases.
Sometimes the definitions of SUT changes, and if the needed structure in a required body content in a Post request changes, we have to change a lot of teststeps in our test.
Say, for instance if for a certain verb its body content
{ "key": string "otherkey": integer "other stuff": .. }
should be
{ "key": array of strings "otherkey": integer "other stuff": .. }
then for each teststep using that verb the body content in a request must be updated
I want to make the setup more configurable. It should be updated with each new yaml definition.
My idea is to generate the body content for each Post request with a Groovy scripted function (to be written).
The function should be based on the yaml file.
All the key-value pairs to be used in the body content should be read from a DataSource teststep in the testcase. Each key in the DataSource block corresponds with the key in Yaml definition.
I want to write code that reads the Yaml file and generates code to generate the needed body content for any given Post request
In halfbaked pseudo code the generated code should read like this:
functionToCreateJsonBodyOfCertainVerb() { jsonBody = [:] if (corresponding property in yaml equals string property) { def someProperty = context.expand('DataSource#someProperty') } if (someProperty != '') { jsonBody['someProperty'] = someProperty } ... return JsonOutput.toJson(jsonBody) }
Problem is that in hierarchy of json body you can have double keys
for instance
{ something: { id: 'blah' }, else: { id: 'blahblah' } }
id is double here and cannot be simply read from a DataSource block with a similar key.
Is there a solution to using keys with the same name?
Or am I making it all way too complicated, and is there already a configurable solution for generating body structures for Post requests?
Greetings, Albert
Hi,
What I did:
I have a Properties block that contains a json Keynames as key in the block, and json paths as values. E.g.
key 'someKey', value 'a.b.c'
This Properties block is placed in a testcase named 'templates'
This properties block is used to store structure information about the body and has the type of request in it's name, e.g. PathsForSomeMessage.
The testcase also contains a template Properties block that contains the same keys as in the other properties block. This is to be used for filling in specific values. The name also contains a reference to the type of request to be used, say ValuesForSomeMessage. E.g.
key 'someKey', value empty
In each testcase where you want to use the type of request you clone the values Properties block and fill in the wanted values, say 'someValue'.
You also create/clone a DataGen teststep with a property for creation of body of wanted message, e.g. 'create somemessag body'. In this you call an external groovy script with the name of the messagetype as input.
That will look like
return com.myproject.soapui.common.JsonBodyContentGenerator.getBodyContent(testRunner,context,log, 'SomeMessage')
In the request of your testcase you fill in the body of the teststep a reference to the DataGen step, '${GenerateParameters#create somemessage body}'
The body generated will look like this
{"a":{"b":{"c":"someValue"}}}
Now if the needed structure of the body content of the body changes in the body structure, say for instance that "c" has to be in node "d" instead of node "b", all you have to do is change the path for key "c" to "a.d.c" in Property block PathsForSomeMessagePathsForSomeMessage. This will change the structure for all messages of SomeMessage, leaving the used value intact. So one generated body will look like: {"a":{"d":{"c":"someValue"}}}
The external Grooovy script I have written:
package com.myproject.soapui.common; import groovy.json.* public class JsonBodyContentGenerator { static getBodyContent(def testRunner, def context, def log, String requestName) { String templateName = 'PropertiesPathsFor' + requestName String valuesName = 'PropertiesValuesFor' + requestName def pathProperties = testRunner.testCase.testSuite.getTestCaseByName("templates").getTestStepByName(templateName) def valueProperties = testRunner.testCase.getTestStepByName(valuesName) def body = [:] for (prop in valueProperties.propertyNames) { def path = 'body.' + pathProperties.properties[prop].value def value = valueProperties.properties[prop].value pathFinder(body, path, value) } return JsonOutput.toJson(body) } static pathFinder(int i, Map structure, String[] paths, String value) { def splitBracket = paths[i+1].split('\\[') def nextPath = splitBracket[0] if (i == (paths.size() - 2)) { if (splitBracket.size() > 1) { def arrayToSet = structure.get(nextPath) if (arrayToSet != null) { arrayToSet.add(value) } else { arrayToSet = [ value ] } structure.put(nextPath, arrayToSet) } else { structure.put(nextPath, value) } } else { if (structure.containsKey(paths[i+1])) { Map mapForKeys = structure.get(paths[i+1]) pathFinder(++i, mapForKeys, paths, value) } else { def keyForMap = nextPath if (splitBracket.size() > 1) { def mapIntoArray = [:] pathFinder(0,mapIntoArray, paths[(i+1)..(paths.size()-1)] as String[], value) def arrayToSet = structure.get(nextPath) if (arrayToSet != null) { arrayToSet.add(mapIntoArray) } else { arrayToSet = [ mapIntoArray ] } structure.put(nextPath, arrayToSet) } else { Map mapForKeys = [:] structure.put(keyForMap, mapForKeys) pathFinder(++i, mapForKeys, paths, value) } } } } static pathFinder(Map structure, String path, String value) { def pathSteps = path.split('\\.') pathFinder(0,structure,pathSteps,value) } }