Overhaul: Test Automation with SoapUI using JsonBuilder and JsonSlurper

I was looking into various ways to do more efficient creation of Json on the fly using the techniques I’ve described in my previous posts. However, while you can do it these ways (which would have to include writing your own parser) there’s a much easier solution, which I’m just glad to have realized sooner rather than later.

I still believe the best way to do automated regression is in a script library away from SoapUI; it gives far more flexibility when the need to make changes inevitably occurs. Keeping that in mind the following approach is not only easier but not hard to implement. Instead of passing each parameter per column in Excel, you can pass the entire Json string in one column. Then, using a combination of the JsonSlurper and the JsonBuilder/StreamingJsonBuilder, you can create valid Json.

So, how do you do it. Consider the following code below, which can be used for any Json you would pass in. The ‘rawJsonString’ is created because if someone pastes json into the Excel cell it can be in a ‘pretty’ format. At least this way we strip out any returns and newlines and let the inbuilt JsonSlurper handle the rest.

The simple function, jsonToMap, creates a new JsonSlurper, parses the text (just like you could do for your assertions), and returns the result.

Finally, we need to actually create our json that will appear in the body of our Rest request. Here’s where the StreamingJsonBuilder comes in. We use StreamingJsonBuilder because we aren’t manipulating or have a need at all for keeping anything in memory. After doing some research this is a cleaner solution and behaves similarly to the JsonBuilder referenced in earlier posts.

The function, CreateObject, takes the parameters ‘root’ and ‘map’. Root is taken from the createJSON function. In most calls you’re either going to have a root with a bunch of given parameters, lists, etc, under it, or it’s just going to be a list of parameters. Since you’re pasting in the rest of the Json in the value of your test case parameter you just need that one value before the output of jsonToMap. If you wanted to you could take out root entirely and just pass the whole json including the root. I think this way provides more possible flexibility going forward. The rest of the function should be self explanatory.

The code that would be in your SoapUI Groovy script is:

def req = new Json()
req.createJSON(testRunner,4,'RootObject')

The code that is used to create the json:

public class Json {

	def createJSON(testRunner,testStepLocation,root) {
		//properties
		def request = testRunner.testCase.getTestStepAt(testStepLocation).getTestRequest()
		def tcProps = testRunner.testCase.getProperties()
		def map
		def rawJsonString
		def prettyPrintJson
		
		//grab objects
		for (p in tcProps) {			
			if (p.value.getValue() != "") {	
					prettyPrintJson = p.value.getValue()	
					rawJsonString = makeSingleLineJson(prettyPrintJson)
					map = jsonToMap(rawJsonString)
			}
		}			
		if (root != null) {
			def object = CreateObject(root, map)
			request.setRequestContent(object)
		} else {
			request.setRequestContent(rawJsonString)	
		}
	}
	
	//create Object
	def CreateObject(root, map) {
		def jsonWriter = new StringWriter()
		def builder = new groovy.json.StreamingJsonBuilder(jsonWriter)				
		def r = builder {						
				"${root}"(map)						
			}				
		def json = groovy.json.JsonOutput.prettyPrint(jsonWriter.toString())
		json = groovy.json.StringEscapeUtils.unescapeJava(json)
		return json
	}
	
	//convert the given json
	def jsonToMap(someJson) {
		def slurper = new groovy.json.JsonSlurper()
		def result = slurper.parseText(someJson)
		return result	
	}
	
	def makeSingleLineJson(prettyPrintJson) {
		def rawJson = prettyPrintJson.replaceAll("\\r?\\n","")
		return rawJson	
	}
}

SoapUI Groovy: Splitting out your scripts

Please see updated post here for a simpler solution to passing the data in. I still use the script library for holding all my scripts.

In previous examples I used if/else statements to separate my test case property data. I also used the same scripts over and over to parse arrays, etc. As JSON calls become more complex you need a new way to create the JSON on the fly while keeping everything legible. To accomplish this I combined the method described in my post about creating objects (here) with a switch statement and some reworking of the overall code.

Lets say we have a JSON call that needs to look like this:

{
    "Object1" : 
    {
        "Object2" : [
            {    
                "Something" : 1,                
                "Something2" : "string",                
                "Something3" : ["strings"]
            }],        
        "Object3" : [
            {
                "Something" : "string",                
                "Something2" : 1,                
                "Something3" : true
            }],        
        "Something" : "string",        
        "Something2" : 1,        
        "Something3" : true      
    }
}

As passed through Excel spreadsheets with 3 columns (Object1, Object2, and Object3), my test case properties look like this:

objectProperties

My ‘main’ groovy script which is called from SoapUI to create the JSON:

package qa

public class CreateObject {

	def createJSON(testRunner, testStepLocation) {

		//properties
		def request = testRunner.testCase.getTestStepAt(testStepLocation).getTestRequest()
		def tcProps = testRunner.testCase.getProperties()
		def object1 
		def object1Map = [:]
		def object2
		def object2Map = [:]
		def object3 
		def object3Map = [:]
				
		//grab objects
		for (p in tcProps) {		
			if (p.value.getValue() != "") {		
				switch(p.key) {
			 
				case 'Object1' :
					object1 = p.value.getValue()
					object1Map = new Tools().fixObjectToMap(object1)	
					break

				case 'Object2' :
					object2 = p.value.getValue()
					object2Map = new Tools().fixObjectToMap(object2)	
					break
			
				case 'Object3' :
					object3 = p.value.getValue()
					object3Map =  new Tools().fixObjectToMap(object3)	
					break
				
				default: 'Default'			
				}
			}
		}	
		//set request json
		def c = new Object().Create(object1Map,object2Map,object3Map)		
		request.setRequestContent(c)		
	}			
}

Some of the ‘Object’ script:

package qa

public class Object {

	//create 
	def Create(object1Map,object2Map,object3Map) {

		def builder = new groovy.json.JsonBuilder()				
		def r = builder.Object1 {		
			if (object2Map.size() != 0) {
				Something3([{
					object2Map.each() {
								key,
								value ->
									if (key.equals('Something3')) {
										value = new Tools().simplisticParse(value, String)
										"${key}" value
									} else {
										"${key}" "${value}"
									}
								}	
				}])
			}
			if (object3Map.size() != 0 ) {
					Object3([object3Map]) 
			}
			if (object1Map.size() != 0) {
					Object1([object1Map])
			}				
		}		
		def json = builder.toPrettyString()
		json = groovy.json.StringEscapeUtils.unescapeJava(json)
		return json
	}	

The ‘Tools’ script referenced above:

package qa

public class Tools {

	//parser for converting strings to arrays
	def simplisticParse(String input, Class requiredType) {
		input.dropWhile {
			it != '['
		}
		.drop(1)
		.takeWhile {
			it != ']'
		}
		.split(',') *.asType(requiredType)
	}
	
	//take what was given and take out the tabs and then make a map from it
	def fixObjectToMap(original) {			
		original = original.replaceAll("\\r?\\n","")			
		def result = original.split('&').inject([:]) { map, token ->
		token.split('=').with { map[it[0].trim()] = it[1] }
		map
		}
		return result
	}	
}

Important things to note:
– All of these scripts reside in the same directory so can be referenced in the form of ‘X_file().Y_method’
– The ‘Object’ script actually contains all the different methods for creating JSON so that’s just a small section of what your overall script could look like

SoapUI: Output TestCase Results to Excel

As you can tell from the rest of my posts, this is again using SoapUI Pro and making use of the DataSink test step and the Extended Script Library.

So, in my test case results, I’d like to see the test case number, description, status, assertion, and the date it was run. In my Excel docs I have each of those as a heading and then SoapUI just starts at the next row. Since I take my initial test cases also from Excel I get the test case Id and description from that Datasource.

The DataSink step I set to be Excel (from the dropdown) and then I get the option to add properties on the left. As mentioned, I create five properties:
1) TestCaseId
2) TestCaseDescription
3) Status
4) Assertions
5) Date

TestCaseId and TestCaseDescription are easy since they are already in my Datasource:

${Datasource#TCId}
${Datasource#TCDesc}

Status can just be called using a Groovy script inline, like so:

${=testRunner.results[testRunner.results.size()-1].status}

The ‘1’ in the above example is the location of the test request in relation to the DataSink.

Assertions references a script in my Script Library, which could look like:

${=new qa.GetFailedAssertions().outputResult(testRunner)}

And then the actual script looks like:

package qa

public class GetFailedAssertions {

	def outputResult(testRunner) {	
		def tcName = testRunner.testCase.name
		def assertionList = testRunner.getTestCase().getTestStepByName(tcName).getAssertionList()
		
		for( assertion in assertionList ) 	{
			for(e in assertion.errors ) {
			return "Assertion Failure: [" + e.message + "]"
			}
		}
	}
}

Date is just today’s date with the time, again inline Groovy script:

${=new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm").format(new Date())}

Now when I run my case all these results are put into the files referenced in the File parameters of the DataSink.

SoapUI Groovy: Disabling a test step within a test case

If you want to disable a specific test step that’s in all your test cases, at the Setup Script level for your TestSuite, here’s what you can do:

def totalTestCases = testSuite.getTestCaseCount();

for(n in (0..totalTestCases-1))	{    
 	if (testSuite.getTestCaseAt(n).getTestStepByName("MyTestStep")){
 			testSuite.getTestCaseAt(n).getTestStepByName("MyTestStep").setDisabled(true)
	 	}
}

Enjoy!

SoapUI: Using the Extended Script Library

Say you’re reusing scripts and also want somewhere you can open the actual groovy script and check it for errors. You can do this in SoapUI Pro by making use of the Groovy Script Library.

So, in SoapUI, you can now reference the script via a Groovy script, like so (this could be all one line but I prefer two):

def cs = new qa.SomeScript()
cs.someMethod(testRunner,4)

Where SomeScript could look like:

package qa

public class SomeScript {

	def someMethod(testRunner,testStepLocation) {

			//properties
			def request = testRunner.testCase.getTestStepAt(testStepLocation).getTestRequest()
			def tcProps = testRunner.testCase.getProperties()
			def map = [:]

			//grab all the properties from the test case and put them in a map as long as they aren't empty
			for (p in tcProps) {
				if (p.value.getValue() != "") {
					map.put(p.key, p.value.getValue())
				}
			}
			def builder = new groovy.json.JsonBuilder(map)
			def json = builder.toPrettyString()
			request.setRequestContent(json)
	}
}

SoapUI Groovy: Slurping JSON in Script Assertions

Groovy has what’s called a JsonSlurper. This takes the given response and ‘slurps’ it so we can assert on various properties. All examples below are for Script Assertions, see my other Groovy post if you’d like to make these into actual Groovy Scripts within a Test Case. Also, a lot of these you can put into various loops (I’ve outlined a few) to check if values aren’t null, etc, but I’ve left them out for the most part just for simplifying the explanations.

See also Test Automation using the JsonSlurper and JsonBuilder for a way to create Json using similar techniques.

1) Assert values are correct for JSON Response

Response:

{
   "Id": 1,
   "Type": "String",
   "GUID": "String-ABC"
}

Script Assertion:

//imports
import groovy.json.JsonSlurper

//grab the response
def ResponseMessage = messageExchange.response.responseContent
//define a JsonSlurper
def jsonSlurper = new JsonSlurper().parseText(ResponseMessage)

//verify the slurper isn't empty
assert !(jsonSlurper.isEmpty())

//verify the Id, Type and Guid aren't null
assert jsonSlurper.Id != null
assert jsonSlurper.Type != null
assert jsonSlurper.GUID != null

//verify Id is 1
assert jsonSlurper.Id == 1

//verify Type and GUID are Strings
assert jsonSlurper.Type == "String"
assert jsonSlurper.GUID == "String-ABC"

2) Assert values are correct a level deep

Response:

{
   "Id": 1,
   "ObjectSummary": {
   "Type": "String",
   "GUID": "String-ABC"
   }
}

Script Assertion:

//imports
import groovy.json.JsonSlurper

//grab the response
def ResponseMessage = messageExchange.response.responseContent
//define a JsonSlurper
def jsonSlurper = new JsonSlurper().parseText(ResponseMessage)

//verify the slurper isn't empty
assert !(jsonSlurper.isEmpty())

//verify the Id, Type and Guid aren't null
assert jsonSlurper.Id != null
assert jsonSlurper.ObjectSummary.Type != null
assert jsonSlurper.ObjectSummary.GUID != null

//verify Id is 1
assert jsonSlurper.Id == 1

//verify Type and GUID are Strings
assert jsonSlurper.ObjectSummary.Type == "String"
assert jsonSlurper.ObjectSummary.GUID == "String-ABC"

3) Assert values that are returned in an array

Response:

{
   "Id": 1,
   "ObjectSummaries": [{
   "Type": "String",
   "GUID": "String-ABC"
   }]
}

Script Assertion:

//imports
import groovy.json.JsonSlurper

//grab the response
def ResponseMessage = messageExchange.response.responseContent
//define a JsonSlurper
def jsonSlurper = new JsonSlurper().parseText(ResponseMessage)

//verify the slurper isn't empty
assert !(jsonSlurper.isEmpty())

//verify the Id, Type and Guid aren't null
assert jsonSlurper.Id != null
assert jsonSlurper.ObjectSummaries[0].Type != null
assert jsonSlurper.ObjectSummaries[0].GUID!= null

//verify Id is 1
assert jsonSlurper.Id == 1

//verify Type and GUID are Strings
assert jsonSlurper.ObjectSummaries[0].Type == "String"
assert jsonSlurper.ObjectSummaries[0].GUID == "String-ABC"

4) A combination of the above using loops and defined functions

Response:

{
   "Id": 1,
   "ObjectSummaries": [{
   "Type": "String1",
   "GUID": "String-ABC1",
   "Description": "StringDescription"
   },
   "Type": "String2",
   "GUID": "String-ABC1"
  }],
  "OverallCount": 2
}

Script Assertion:

import groovy.json.JsonSlurper

def ResponseMessage = messageExchange.response.responseContent
def jsonSlurper = new JsonSlurper().parseText(ResponseMessage)

def i = 0
def x = 0

//as long as there are values in the array
while(jsonSlurper.ObjectSummaries[i]!=null){

  id = jsonSlurper.ObjectSummaries[i].Id
  type = jsonSlurper.ObjectSummaries[i].Type
  GUID = jsonSlurper.ObjectSummaries[i].GUID
  type = type.toLowerCase()

  if (CheckIfDescription(jsonSlurper.ObjectSummaries[i]) == true) {
  		//do something here
  }

  if (type.equals("String1") && CheckIfDescription(jsonSlurper.ObjectSummaries[i]) == true) {
      //you'd have your assertion here
  } else if (type.equals("String1") && CheckIfDescription(jsonSlurper.ObjectSummaries[i]) == false) {
     //another assertion
  }  else  {
    //some other random count assertion
   x++
  }
 i++
}

assert jsonSlurper.OverallCount == i

//function to check if Description exists
def CheckIfDescription(value)
{
  if (value.containsKey("Description") && value.Description != null) {
    return true
  } else {
    return false
  }
}

SoapUI Groovy: Creating valid JSON from Test Case Properties

Examples of creating valid JSON through SoapUI test case properties


Here’s some examples of how you can take values passed in from datasources (like Excel) and make them into valid JSON on the fly. The variable ‘request’ in each script is the test step that you are writing the JSON to.

Most basic form, create a map, convert the map to JSON
import groovy.json.*

//properties
def request = testRunner.testCase.getTestStepAt(4).getTestRequest()
def tcProps = testRunner.testCase.getProperties()

def map  = [:]

for(p in tcProps){		
 	if (p.value.getValue() != "") {
 		map.put(p.key, p.value.getValue())  					
	}
}


//create valid JSON
def builder =  new JsonBuilder(map)
def json = builder.toPrettyString()
request.setRequestContent(json)
Create an object from your properties
import groovy.json.*

//properties
def request = testRunner.testCase.getTestStepAt(4).getTestRequest()
def tcProps = testRunner.testCase.getProperties()

def map = [:]

for(p in tcProps){		
 	if (p.value.getValue() != "") {
 		map.put(p.key, p.value.getValue())  					
	}	
}


//create valid JSON
def builder =  new JsonBuilder()

def root = builder.SomeObject {	
	map.each() {key,value -> "${key}" "${value}"}
}

def json = builder.toPrettyString()
request.setRequestContent(json)
Take an object which is already in the properties (so you’ve already written the JSON out) and just put it to the request
import groovy.json.*

//properties
def request = testRunner.testCase.getTestStepAt(4).getTestRequest()
def tcProps = testRunner.testCase.getProperties()

def map  = [:]

for(p in tcProps){		
 	if (p.value.getValue() != "") {
 		map.put(p.key, p.value.getValue())		
	}	
}

//create valid JSON
def builder =  new JsonBuilder()

def root = builder {
	MyObject {
		map.each() {key,value -> "${key}" "${value}"}
}

def json = builder.toPrettyString()
//need to do this if using latest Groovy or else it'll escape all your quotes, etc
json = StringEscapeUtils.unescapeJava(json)
request.setRequestContent(json)
Create int arrays as valid JSON

This uses the method dropWhile and takeWhile which were added in 1.8.7 of Groovy (note: this is also the version that fixes the bug that then breaks the passing of objects so you need to unescape the json) and the function called simplisticParse, which I didn’t write, that credit goes to Tim Yates and saved me so much time.

import groovy.json.*
//properties
def request = testRunner.testCase.getTestStepAt(4).getTestRequest()
def tcProps = testRunner.testCase.getProperties()
def map  = [:]

//grab all the properties from the test case and put them in a map as long as they aren't empty or contain CD
for(p in tcProps){		
 			if (p.value.getValue() != "") { 
 			map.put(p.key, p.value.getValue()) 				
	}	
}

//create valid JSON
def builder =  new JsonBuilder()
def root = builder {
	map.each() {key,value -> 
	if (key.equals('IntArrayKey1') || key.equals('IntArrayKey2') || key.equals('IntArrayKey3')) {
		value = simplisticParse(value, Integer)
	"${key}" value
	}else {
		"${key}" "${value}"
	} 				
	}
}

def json = builder.toPrettyString()
request.setRequestContent(json) 

//parser for fixing JSON
def simplisticParse( String input, Class requiredType ) {
  input.dropWhile { it != '[' }
       .drop( 1 )
       .takeWhile { it != ']' }
       .split( ',' )*.asType( requiredType )
}