Forum Discussion

AlbertSoapUI's avatar
AlbertSoapUI
Contributor
6 years ago
Solved

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)
        }
    }

     

4 Replies

  • 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)
        }
    }

     

    • groovyguy's avatar
      groovyguy
      Champion Level 1

      Thanks for coming back and sharing how you tackled this. That groovy script looks pretty handy!

    • TanyaYatskovska's avatar
      TanyaYatskovska
      SmartBear Alumni (Retired)

      Thanks for sharing your code with us, AlbertSoapUI!

      Starting from next week, API Summer's task will be to post as many new topics as possible. Feel free to share more your samples! You will have great chances to win.

  • sanj's avatar
    sanj
    Super Contributor

    Can you not just have this as tc properties and then change them in the request using groovy?

    That may be an easier solution.