February 12, 2018

Getting Groovy with Ready API

Welcome to our new monthly techie blog…

Ready API is a web service testing suite produced by SmartBear. It started out as a single product, SoapUI and allowed users to write tests for SOAP-based web services over ten years ago. It has a feature-rich UI that allows you to write many tests without the need to write any code. Yet, often the wide array of pre-canned tools aren’t enough and you have to get your hands dirty.

To handle more complex testing scenarios, you have three options: install an existing plug-in, write a plugin in Java, or use scripting. This posts focuses on choice three, the one most applicable to my day-to-day work at Jeeng. There are two scripting languages available within Ready API: Groovy and JavaScript.

JavaScript needs no introduction: every web browser runs JavaScript and it changed the world. Groovy isn’t as well-known outside of the Gradle and Java communities. It’s a scripting language for the JVM and has its own powerful constructs but allows you to use Java libraries when you need them. Ready API provides the most support for Groovy. Most of the examples you’ll find use it.

This post will not cover the basic syntax of Groovy. If you want to familiarize yourself with it, here’s a tutorial.

Gettin’ Groovy

To choose a scripting language within Ready API, you set the value in the Project Properties. The Project Properties are in the SoapUI tab:

I test JSON-based web services at Jeeng. Ready API supports JSON and has the basics covered, but most of the existing functionality handles validating XML-based payloads. This often requires using Groovy in my test step assertions. You can find the script assertion in the UI here (as of Ready API 2.2):

Here’s an example of a test assertion I use scripting for:

[groovy]
// imports the JsonSlurper class, which parses JSON and converts it to a dictionary or map representation for you

import groovy.json.JsonSlurper

// init
def slurper = new JsonSlurper()

// messageExchange is a SoapUI object that gives access to the request and response for the current test step

def response = messageExchange.response.contentAsString

def json = slurper.parseText(response)

assert json != null

// it’s possible that the response wasn’t in proper JSON format and is deserialized as empty
assert !json.isEmpty()

// check that each content only has one tag and that its subtitle is the expected value

// the data field is specific to the object returned from the web service and is not part of the JsonSlurper implementation

// each is a Groovy method that iterates through every element in a collection and executes the given expression on each element

// within the closure created by each, the it variable references the current item in the collection

// assert checks that an expression evaluates to true and throws an exception if it doesn’t
json.data.each { assert it.tags.size() == 1 it.subtitle.value == SubtitleValue }
[/groovy]

Check Out a Script in the Library

I’m not an expert at JSON path matching, but I didn’t see an easy way to do this from within the UI. You’ll notice much ceremony with deserializing the response you’d likely use every time.  You don’t need to copy and paste this code throughout your project. Instead you can create a Script Library.

A Script Library is a folder with .groovy script files in it, or .js files if you’ve chosen to use JavaScript. You set the path to the library in the project properties in the SoapUI tab, right by the Script Language setting.

Here’s the script file I created for deserializing JSON responses:

[groovy]

import groovy.json.JsonSlurper

class JsonSlurperUtil {

def static parseJsonResponse(messageExchange) {
assert messageExchange != null

def json = parseJsonResponseFromString(messageExchange.response.contentAsString)
return json
}

def static parseJsonResponseFromString(response) {
assert response != null
assert !response.isEmpty()

def slurper = new JsonSlurper()
def json = slurper.parseText(response)
assert json != null

return json
}
}
[/groovy]

Below is an assertion where I use my JsonSlurperUtil class. You’ll notice I don’t have to import any namespaces. You can choose to namespace your scripts if you wish. If you don’t namespace them, they are available to use in your scripts with no import required.

[groovy]
def response = JsonSlurperUtil.parseJsonResponse(messageExchange)
assert !response.isEmpty()
assert response.data != null

def source = response.data
assert source.variations.size() == 1

def targets = source.variations[0].targets[0]
assert targets.size() == 3

// any is a Groovy method that returns true if any element in the collection returns true in the expression defined in the closure
assert targets.any{ it.region == “au” }
assert targets.any{ it.region == “ca-on” }
assert targets.any{ it.region == “us-ma” }
[/groovy]

Step Up

An assertion won’t always do the job. Sometimes, you need to extract and transform data from many sources, or you may need to call other endpoints. In these cases, you’ll want to use a Groovy Script test step.

Below is a script step I use to extract data from several steps in the test case and transforms the data into a dictionary or map. Stripes are units that contain advertisements displayed by our publishing customers. Sources define where the ads come from our advertising customers. This dictionary maps stripes to sources.  The script saves the dictionary in the Properties step of the test case because it’s used in a later test step.

[groovy]

// the JsonOutput class is used to serialize objects to JSON
import groovy.json.JsonOutput

// testRunner is a SoapUI object that provides access to projects, test suites, cases, and steps
// the testCase field defaults to the currently executing test case
def propertiesStep = testRunner.testCase.testSteps[‘Properties’]
assert propertiesStep != null

// TestRequestUtil is another class I wrote to make extracting data from test steps easier
// the source code for that class will follow this example
def sourceResponse = TestRequestUtil.getResponseContentFromStep(testRunner, ’01a Get all sources’)
assert !sourceResponse.isEmpty()

def sources = JsonSlurperUtil.parseJsonResponseFromString(sourceResponse)
assert !sources.isEmpty()

// findAll is a Groovy method that retrieves all of the items in the collection that return true from the given expression
// collect is a Groovy method that is used here to extract the id field from each item that was returned from findAll
def sourceIDs = sources.findAll{ it.isActive }.collect{ it.id }
assert !sourceIDs.isEmpty()

propertiesStep.getProperty(‘sourceIDs’).setValue(JsonOutput.toJson(sourceIDs))

def stripesResponse = TestRequestUtil.getResponseContentFromStep(testRunner, ’02a Get all stripes’)
assert !stripesResponse.isEmpty()

def stripes = JsonSlurperUtil.parseJsonResponseFromString(stripesResponse)
assert !stripes.isEmpty()

def stripeIDs = stripes.findAll{ it.isActive }.collect{ it.id }
assert !stripeIDs.isEmpty()

propertiesStep.getProperty(‘stripeIDs’).setValue(JsonOutput.toJson(stripeIDs))

// create an empty map
def stripeSourceIDs = [:]
stripes.findAll{ it.isActive }.each{ stripeSourceIDs.put(it.id, it.contentSourceIDs) }
assert !stripeSourceIDs.isEmpty()

propertiesStep.getProperty(‘stripeSourceIDs’).setValue(JsonOutput.toJson(stripeSourceIDs))
[/groovy]

And here is the source for the TestRequestUtil class:

[groovy]
class TestRequestUtil {
def static getResponseContentFromStep(testRunner, step) {
assert testRunner != null
assert step != null
assert !step.isEmpty()

def testStep = testRunner.testCase.testSteps[step]
assert testStep != null

def testRequest = testStep.testRequest
assert testRequest != null

def response = testRequest.response
assert response != null

def content = response.contentAsString
return content
}
}
[/groovy]

Outro

Through this post I hope I’ve shown you ways to leverage the scripting available to you in Ready API. It’s powerful and something every tester should learn.  Your job will be easier, and it’s possible to create more meaningful tests for your projects. I use Ready API to test cloud-based services we operate at Jeeng, ensuring a continuous flow of quality releases. If you have questions or suggestions, please leave them in the comments. Thanks for reading!