Beginning soapUI Scripting 4: Lists, for, and if

Many of the methods you'll encounter in soapUI return lists, objects that can contain multiple data items.  In this post we'll cover some of the techniques and methods for working with lists, the for statement-- which is frequently used with lists, and the if statement, used for basic control flow.  For this post we'll actually use these concepts (and some of the others previously covered) with HTTP test requests, illustrating their practical application in soapUI.

Working with Lists in Groovy

In Groovy, you can create a list using the following syntax:

def myList = ["One", "Two", "Three", "Four"]

This creates a list of four strings.  Note the use of brackets ([ and ]) to enclose the items in the list; each item is also separated by a comma.  You can retrieve individual items in the list like this:

myList[0]

This would return the first item in the list-- "One" in this case.  The format is listName[x], where x is the zero-based position, or index, of the item in the list.  By zero-based, I mean we start counting at zero when determining an item's index: the first item in the list is at index 0, the second is at index 1, the third is at index 2, etc.

Since lists are objects, they have their own methods; here's some code illustrating a few of them:

def myList = ["One", "Two", "Three", "Four"]
log.info("List contents: $myList")
//size()-- returns number of items in the list
log.info("Size is " + myList.size())
log.info("Element one is " + myList[0])
//indexOf()-- returns the index of a given item in the list
log.info("Index of 'Four' is " + myList.indexOf("Four"))
//add()-- adds an item to the list
myList.add("Five")
log.info("After adding 'Five', size is " + myList.size())
log.info("Element five is " + myList[4])
//remove()-- removes an item from the list
myList.remove("Two")
log.info("After removing 'Two', size is " + myList.size())
log.info("List contents now: $myList")

The output from this script:


A SoapUI Project Using Lists

Now let's take a look at our example project; you can download a zipped version of it here. Download it, unzip it, and import it into soapUI.  The test suite consists of a single test case with four HTTP test requests; the service we're testing takes a zip code and returns the U.S. state where the zip code is located.

The SetZipList Groovy Script step establishes a list of zip codes to use with the HTTP requests and sets it as a context property (note that using a list here is done for illustrative purposes; in truth it would probably be more practical to just hard code the zip codes in each test step):

context.zipList = ["19106","20500","10118","57751"]

The members of the list are plugged into each HTTP request using a special form of property expansion.  Here's a screenshot of the first HTTP request step:


Note the value for the ZipCode parameter of our request-- this is a property expansion expression incorporating Groovy Script.  The "$" and enclosing braces are standard property expansion syntax; however, the "=" signifies that what follows within the braces is Groovy Script.  context.zipList[0] returns the first item in the list we created in the first test step ("19106"), using it as our ZipCode parameter in the request.  Each HTTP request step is set up the same way, with each retrieving a different item in the list (the second request gets the second item, the third the third item, etc.).

The following script is in the test case tear down:

def resultsList = testRunner.getResults()

for(res in resultsList){
    if(res.getTestStep().getClass() == com.eviware.soapui.impl.wsdl.teststeps.HttpTestRequestStep){
        def tStep = res.getTestStep()
        log.info(" Step Name = " + tStep.getName())
        log.info("    URL = " + res.getProperty("URL"))
        log.info("    Status = $res.status")
        log.info("    Time Taken = $res.timeTaken ms")
    }
}

This script retrieves select information from our test results and writes them to the log.  The first line uses the getResults() method of the test case's built-in testRunner object to retrieve the results of the test case's test steps, returned in a list.

Using for and if

Frequently when dealing with a list, you'll want to iterate through it and perform some action or set of actions on each individual item.  A for statement is one way to do this.  The basic syntax:

for (varName in list) {
     Set of actions to perform...
}

This takes each item in list list and assigns it to the variable varName, then performs the actions contained in the braces ({ and }).  The code is repeated for every item in the list.  So in our script above, the first result in resultsList is assigned to variable res and the code in the brackets is executed.  If there's another item in the list, that item then gets assigned to res and the code in the brackets is repeated, and so on, until all the items in the list have been processed.

You should recognize most of the code within the brackets as method calls, but the line starting with if may be unfamiliar.  There's a problem in our for loop-- it iterates over every item in our list of results, including results from the first test step, the Groovy Script test step.  Consequently, not all of the method calls we attempt to make are valid-- the getProperty() call, for example, would fail when we tried to call it on the results from the first test step.

The if statement allows us to run or not run code based on certain criteria.  The basic syntax for the if statement:

if (test expression that evaluates to true or false){
     Code to execute if test expression is true
}[else{
     Code to execute if test expression is false
}]

The else clause is optional, and in fact there's no else clause used in our example.  The if statement there checks to see if our results were generated by an HTTP test request step-- if they were (the expression evaluates to true), then the code in the brackets is safe to execute; if they weren't, the code in the brackets is skipped.  Note the operator used to check for equality-- == and not = as you might expect.  Other basic comparison operators include > (greater than), < (less than), >= (greater than or equal to), <= (less than or equal to), and != (not equal to).

Also note the indentation used in the script; with each block an extra level of indentation is used.  While not strictly required, this is considered good practice for readability-- you can easily see where the for block and if block begin and end.

Finally, here's the output in the script log produced by the tear down script:

Beginning soapUI Scripting 3: Setup and Tear Down, Context Properties

You can download a "shell" project to import into soapUI and use for scripting here.

In all the scripting we've done up until this point, we've worked exclusively with Groovy Script test steps.  It's time to introduce some more of soapUI's scripting components: setup and tear down scripts. These aren't test steps per se; rather, they're special scripting components available for test cases and test suites through their editors.  The screenshot below shows the buttons to access a test suite's setup and tear down scripts highlighted in blue (the buttons are in pretty much the same place in the test case editor):


At the test suite level, setup scripts are run prior to any of the suite's test cases and tear down scripts are run after the last test case is completed.  At the test case level, setup scripts are run prior to any of the test case's test steps and tear down scripts are run after its last test step is completed.  The resulting order (assuming each component exists): 1) suite setup, 2) test case setup, 3) test case test steps, 4) test case tear down (steps 2 through 4 are repeated for any additional test cases), 5) suite tear down.

Now that we have different scripting components, let's consider the problem of sharing variables between them.  You might want to do this for the purposes of looping (for a counter variable, for example), when working with a file whose contents need to be re-used among components, etc.  By default, variables created in one component aren't available in others.  For example, put the following code into a test case setup script:

def myVar = 1
log.info("In test case setup: myVar = $myVar")


And this in one of the test case's test step:

log.info("In test step: myVar = $myVar")

This code creates a variable called myVar in the test case setup script and prints out a message with its value (for more details about how this line works, see the previous post on strings).  The intention with the test step is to print out the same value using the same variable.  If you try to run this test case, though, it fails.  The setup script message appears in the script log as expected, so the variable seems to be created successfully:


The test step message never gets written to the script log, however.  If you look at the error log, you'll see a message that looks something like this:

Mon May 13 23:11:24 EDT 2013:ERROR:groovy.lang.MissingPropertyException: No such property: myVar for class: Script1

Basically, our test step doesn't recognize myVar.  In the script's current form, myVar only exists in the test case setup script, so it's unavailable to any other component-- you'll encounter an error even if you try to access it from the test case teardown script.

This is a situation where the context variable comes into play.  Like the log and testSuite or testCase variables, context is a built-in variable automatically made available by soapUI; it's indicated in the messages above script windows-- e.g., the "Setup Script is invoked..." message in the test suite editor screenshot above.  You can add your own user-defined variables as properties of context objects (for those of you familiar with user-defined test case and test suite properties, context properties are not the same thing).  This can be done explicitly via the setProperty() method, but you can also treat your variable as a property using dot notation.  Hopefully an example might clarify.  If you put this code in a test case setup script (remember, the lines starting with "//" are comments for clarification-- nothing's executed by the Groovy interpreter):

//Creating a context property via setProperty() method (the long way)
context.setProperty("myString", "Hello World!")
//Creating a context property via direct dot notation (the short way)
context.myString2 = "Hello World (again)!"

And put this into a Groovy Script test step within that test case:

//Getting a context property via getProperty() method
log.info(context.getProperty("myString"))
//Getting a context property using direct dot notation
log.info(context.myString2)

You'll get the following log output:



A few things to note about this example: the context properties are available between components at different levels, created at the test case level but accessible at the test step level.   Also, context.setProperty("myString", "Hello World!") and context.myString = "Hello World!" are functionally equivalent, as are context.getProperty("myString") and context.myString.

The context variable at the test case and test step levels seems to reference the same object; the context variable at the test suite level references a different object, but context properties set in the test suite setup script are still subsequently available at the test case and test step levels.  For example, you could move the test case setup code above to the test suite setup script and you'd still get the same log output.  However, note that while context properties appear to propagate downward (from test suite to test case and test step), they don't propagate up-- context properties created in a test case or test step will not be available with the context variable in the test suite tear down script.

Beginning soapUI Scripting 2: Strings

You can download a "shell" project to import into soapUI and use for scripting here.

Like my first post on scripting, the material in this post is not specific to soapUI, but strings are used so extensively I thought they were worth a brief explanation for beginners who may not be familiar with them.  Put simply, strings are text data, or a sequence of characters; "Hello World!" is an example of a string. The syntax to create a string variable is similar to the syntax we saw in my first post when creating an integer variable:

def myVar = 3
def myString = "Hello World!"


These lines assign the value 3 to the variable myVar and the string "Hello World!" to the variable myString.  Note the use of quotes, one key difference with strings-- those demarcate our text, but are not included with the actual data.  As with the integer variable myVar, once the myString variable has been created, we can reference its value in subsequent lines of our script using its name:

log.info(myVar)
log.info(myString)


This code prints out the values of our variables to the log:



As promised, the enclosing quotes used when the string was created are not included when the string is printed.

Strings can be enclosed in either single quotes or double quotes in Groovy (but not in "pure" Java-- in Java, strings are always enclosed in double quotes).  You can use a mix of both when you want to include one or the other in your string.  If you want to include a single quote as part of your string, enclose the string in double quotes, and vice versa; for example:

def stringOne = "Don't panic."
def stringTwo = '"Look, I have double quotes!"'
log.info(stringOne)
log.info(stringTwo)


This produces:



You can also perform variable substitutions in Groovy; variable names preceded by the "$" character that are enclosed in strings are replaced with their underlying values.  For example:

def myVar = 3
def myString = "The value of myVar is $myVar"
log.info(myString)


The resulting output:



One thing to remember: variable substitution only works with strings enclosed in double quotes-- in a  string enclosed in single quotes, $myVar would be interpreted as a literal (simply printed out "as is" as  "$myVar").

Finally, while strings may behave in some ways like Java's so-called primitives (the basic data types that include integers), they are in fact objects of class String.  Consequently, they have quite a few methods at their disposal that can be used to parse and alter their contents.  There are far too many methods to cover all of them here; see the Java API entry for the String object to see the full list.  Here's some code that demonstrates a few of them, however (the lines that start with "//" in the script are comments-- they're only there for informational purposes):
def stringOne = "   One, two, three    "
log.info("stringOne = '$stringOne'")
//Trim method-- removes leading and trailing whitespace from a string
def stringTrimmed = stringOne.trim()
log.info("Trimmed string = '$stringTrimmed'")
//Contains method-- checks if one string is contained in another
log.info("String 'two' in '$stringTrimmed'?")
log.info(stringOne.contains("two"))
//Length method-- returns the number of characters in the string
log.info("Length of '$stringTrimmed':")
log.info(stringTrimmed.length())
//EndsWith method-- checks if string ends with certain characters
log.info("'$stringTrimmed' ends with 'three'?")
log.info(stringTrimmed.endsWith("three"))
//StartsWith method-- checks if string starts with certain characters
log.info("'$stringTrimmed' starts with 'Two'?")
log.info(stringTrimmed.startsWith("Two"))
And here's the output: