SoapUI & Soapy tutorial part 01 – how to install soapy

I’ve decided to prepare a SoapUI video tutorial. Maybe some of you will find it useful.

In the first video I’m showing how to install Soapy library in the free version of SoapUI.
Soapy is a helper library that I’ve created in my current company. It basically is a bunch of helpers that can ease the pain of SoapUI user 🙂
In upcoming videos I’ll try to introduce some of new ones and also few that were already mentioned on my blog 🙂

How to generate a cURL command out of SoapUI Pro REST request

After few attempts I finally made it working properly 🙂

To make use it, simply add a new script assertion to your REST request, then paste those two lines:

import com.yelllabs.soapy.helpers.CurlGenerator;
new CurlGenerator(context, messageExchange, log);

and check what will come up in the Script log tab 🙂
A nicely formatted cURL command with all the query parameters, headers and msg body.

ps. of course before that create a CurlGenerator.groovy file in {script_bibrary}/com/yelllabs/soapy/helpers/ directory 🙂

package com.yelllabs.soapy.helpers;

import com.eviware.soapui.impl.wsdl.teststeps.RestResponseMessageExchange;
import com.eviware.soapui.impl.wsdl.testcase.WsdlTestRunContext;
import com.eviware.soapui.support.types.StringToStringsMap;
import org.apache.log4j.Logger;


/**
* @brief can create cURL command out of REST request.
* @author Janusz Kowalczyk
* @created 2012-07-04
* @updated 2012-11-14 Janusz Kowalczyk
*/
class CurlGenerator{

    private def context;
    private def messageExchange;
    private def request;
    private Logger log;
    private String testCaseName;
    private String reqHttpMethod;
    private String reqContent;
    private String reqContext;
    private String reqEndpoint;
    private byte[] reqRawData;
    private StringToStringsMap reqHeaders;
    private def reqParams;

    /**
    * @brief Default constructor
    *
    * @param context - test run context
    * @param messageExchange - message exchange availabe in a assertion script
    * @param log - Logger
    * @param curlParams - cURL params that will be placed at the begining of cmd
    */
    CurlGenerator( WsdlTestRunContext context, RestResponseMessageExchange messageExchange, Logger log, String curlParams ) {
        this.context = context;
        this.messageExchange = messageExchange;
        this.log = log;

        if ( this.context.getCurrentStep().isDisabled() == false ) {
            init();
            log.info getCurlCmd(curlParams);
        }
    }


    /**
    * @brief Another constructor, this one will use "-ki" as default cURL params
    *
    */
    CurlGenerator( WsdlTestRunContext context, RestResponseMessageExchange messageExchange, Logger log) {
        this(context, messageExchange, log, "ki");
    }


    /**
    * @brief Init method that gets all the needed data from context and msgExchng
    *
    */
    private void init() {
        this.request = messageExchange.getRestRequest();
        this.testCaseName = this.context.getCurrentStep().getLabel();
        this.reqHttpMethod = this.request.getMethod();
        
        // gets the req body if there's one
        // if not the an empty List is returned
        this.reqContent = (this.request.hasRequestBody()) ? this.context.expand(this.request.getRequestContent()) : ""; 

        this.reqParams = this.request.getProperties();

        this.reqContext = this.context.expand(this.request.getResource().getFullPath());
        this.reqEndpoint = this.context.expand(this.request.getEndpoint());

        // get request headers
        // works only when request is made manually for the second time, in other case it will be always [:]
        this.reqHeaders = (request.getResponse() != null) ? request.getResponse().getRequestHeaders() : [:];
        this.reqRawData = (request.getResponse() != null) ? request.getResponse().getRawRequestData() : [];
    }


    /**
     * @brief Will use "-ki" as default curl parameters
     * 
     * @return a cURL command with "-ki" set as default parameters
     */
    public String getCurlCmd() {
        return getCurl("ki");
    }


    /**
     * @brief 
     * 
     * @param String curlParams - Pass the curl parameters. If "" is used then no params will be added to the command line
     *
     * @return A cURL command with custom parameters
     */
    public String getCurlCmd(String curlParams) {
        return getCurl(curlParams);
    }


    /**
    * @brief Will create a cURL command with provided params
    *
    * @param curlParams custom cURL params
    *
    * @return a cURL command with custom params
    */
    private String getCurl(String curlParams)
    {
        String params = ( curlParams.isEmpty() ) ? "" : "-" + curlParams;
        String cmd = "curl %s %s \"%s%s\" %s %s";
        return String.format(cmd, params, getMethod(), getUri(), getParams(), getHeaders(), getContent());
    }


    public byte[] getRawRequestData(){
        return this.reqRawData;
    }

    /**
    * @brief return a list of maps of sent non-empty query parameters
    *
    * @return return a list of maps of sent non-empty query parameters
    */
    private List getSentParams(){
        List params = [];
        this.reqParams.each{
            k,v ->
                if ( !v.getValue().isEmpty() )  {
                    def param = [ "name": v.getName(), "val" :  context.expand( v.getValue() ) ]
                    params.push( param )
            }
        }
        return params;
    }

    /**
    * @brief Converts list of query params into a nicely formatted string
    *
    * @return A string representing all the query params
    */
    private String getParams(){
        String qp = "";
        if ( false == getSentParams().isEmpty() ) {
            getSentParams().each{
                p ->
                // insert ? when processing first param, else insert & 
                    qp += ( qp  == "" ) ? "?" + p.name + "=" + p.val : "&" + p.name + "=" + p.val;
            }
        }
        return qp;
    }

    private String getMethod(){
        return "-X" + reqHttpMethod;
    }

    private String getUri() {
        if ( this.reqContext.toString().toLowerCase().contains( this.reqEndpoint.toString().toLowerCase() ) ){
            return this.reqContext;
        } else {
            return this.reqEndpoint + reqContext;
        }
    }

    private String getHeaders() {
        if ( this.reqHeaders.isEmpty()) {
            return "";
        } else {
            String H = "";
            reqHeaders.each{
                key, val ->
                    H += ' -H "' +key + ':' + val[0] +'"'
            }
            return H;
        }
    }

    private String getContent() {
        if ( this.reqContent.isEmpty() ) {
            return "";
        } else {
            return "-d '" + this.reqContent+"'";
        }
    } 

}//end

Introducing Soapy – a helper lib for SoapUI

Hi All,

This project is really an immature one, with a mixture of Java and Groovy, lacking loads of the documentation, so use it at your own risk 🙂
After those few words of encouragement I must say that actually without this lib my friendship with SoapUI would end months ago.
I don’t have much time to describe what kind of stuff you can find in there, so please explore the src code. There’s one example at the end of this post that I find pretty useful.
The best thing is that you can use it with the free version of SoapUI.

As mentioned before, many classes are still missing a decent documentation, but I’m still hoping that one day everything will better and sun will shine for all of us!
Before asking any questions, please read the README.md file and/or read the code itself 🙂

To compile the project you’ll need:

  • Git
  • Java
  • & Maven

Once the build process is done, simply put the soapy-1.0.jar from ./target directory to your SoapUI/bin/ext folder, then restart the SoapUI.
If everything went fine, then you should see a similar entry in soapUI log:

Fri Oct 05 16:41:03 BST 2012:INFO:Adding [/home/jk7/git/soapui-runner/soapui-4.5.1/bin/ext/soapy-1.0.jar] to extensions classpath

To make any use of it, import any Soapy class into your script step or script assertion:

import com.yelllabs.soapy.”name.of.the.class”

Ahh… I almost forgot, here’s the link to my Soapy project on GitHub 🙂

Cheers,
J

How to parse and handle a JSON response:

import com.yelllabs.soapy.helpers.Response;
import com.yelllabs.soapy.json.JSONHandler;

Response r = new Response( context, testRunner, log, "nameOfTheStepWithJSONResponse" );
JSONHandler jh = new JSONHandler( log );
Object json = jh.parse( r.getRawResponse() );
// validate the JSON 
jh.isValid(json); 
jh.isObject(json);

// then to access any element, simply:
json.elementName.arrayName[positionInArray].etc

Adding a ‘Sentinel’ script to SoapUI that will clean up the test environment after the test suite run

After following just two steps described below you’ll be able to run any test case after the whole Test Suite finished its run.
This might be useful when you have to clean up the mess your test suite made to the test environment 🙂

OK, enough gibberish, let’s make it happen. What we actually have to do is :

  1. Create a test case(s) that will do the clean up after the test suite run. (*)
  2. Add a TearDown Script to your Test Suite

* – disable them, to exclude them from the main test suite run.

Here’s the screenshot with my configuration:

sentinel script

sentinel script

And here’s the code that we need to add to a TearDown Script in your Test Suite:

// this is needed to run a test case
def properties = new com.eviware.soapui.support.types.StringToObjectMap()

// "sometimes" I have to work with unstable builds, 
// so to make sure that I can actually access anything I've added this 
// test case that checks the API availability (it makes a simple request towards the API and fails if there's no proper response)
def checkAPIAvailability = testSuite.getTestCaseByName( "checkAPIAvailability" )
// a test case that makes all the requests and runs all the scripts needed to clean up the environment after the test suite run
def runCleanUpStep = testSuite.getTestCaseByName( "cleanUpEnvAfterTestrun_-_RegularUser" )
// counter that gives a bit more control on for how long you want to check for the API availability
int counter = 1;

// Do the first check
boolean status = ( checkAPIAvailability.run(properties, false).status.toString().equals("FINISHED") ) ? true : false;
// EDIT: Had to change the expression from checking if it failed to the one that checks if it actually passed 
// boolean status = ( checkAPIAvailability.run(properties, false).status.toString().equals("FAILED") ) ? false : true;


// in case the API is not available then loop the whole check thing till it is 🙂
// or stop after 20 loops 
// (consider adding a delay step to the availability check test case which will decrease the number of unnecessary calls
while ( !status && counter <= 20){
	counter++;
	// EDIT: same as above, had to change the expression to handle some strange cases 🙂
	status = ( checkAPIAvailability.run(properties, false).status.toString().equals("FINISHED") ) ? true : false;
}

// I don't know why I left that additional "if" but it is still there just in case ....
if (status){
	runCleanUpStep.run(properties, false)	
}

SoapUI: add custom query param to all service requests

I had a busy day today with SoapUI 🙂

Another script for you guys, will add/remove query parameter to all service resources.

import com.eviware.soapui.impl.rest.RestResource

def project = testRunner.testCase.testSuite.getProject()
List<RestResource> ops = project.getInterfaces()["your_service_name"].getOperationList()

String paramName = "debug"
String paramValue = "true"
int addCnt = 0;
int remCnt = 0;
int errCnt = 0;
int noOfOper = ops.size()

for (RestResource res : ops)
{
	res.each{
		oper -> 
			Boolean result = toggleDebug(oper, paramName, paramValue)
			if ( result == null) 
				{ 
					errCnt++; 
				}
			else if (result) 
				{ 
					remCnt++; 
				}
			else if ( !result )
				{
					addCnt++;
				}
	}
}

log.info "DEBUG parameter was ADDED: " + addCnt + " times, REMOVED: " + remCnt + " times. Errors: " + errCnt + ". Number of all srvs operations: " + noOfOper

public Boolean toggleDebug(RestResource oper, String paramName, String paramValue)
{
		if ( oper.getProperties().containsKey(paramName) )
		{
			oper.removeProperty(paramName)
			log.info "DEBUG mode query param was REMOWED from operation: '"  + oper.getName() + "'"
			return true;
		}
		else
		{
			oper.addProperty(paramName)
			oper.getProperties()[paramName].setDefaultValue(paramValue)
			log.info "DEBUG mode query param was ADDED to operation: '"  + oper.getName() + "' with value: " + paramValue
			return false;
		}
		return null;
}

SoapUI: switching between different project configurations

Woohoo!

Today I’ve got another SoapUI script, that can be used to quickly change between project configurations for different environments, i.e.: DEV, STAGING, PROD etc.

Before you start executing the script below, create two text files in the same dir as your SoapUI project file, named:
DEV.properties
STAGE.properties

Once you’re done, execute the script.
In result it will create/update project property called “MainEdnpoint”.

Later on, you can take advantage of such property by replacing all the service endopoints by only one value:
${#Project#MainEdnpoint}
This way, all your request will point at the host you want to currently run test against. This is very simple & flexible way to manage you scripts for all different environments.

Same way you can add another property, like: “serviceContext” which will point at current context path independent from any changes made by devs/ops
To use this property, just change you service request path:
from i.e: /service-v0.1/listPosts
to: /${#Project#serviceContext}/listPosts

String env = "DEV"
def filename = context.expand( '${projectDir}'+File.separator+File.separator+env+'.properties' )
def file = new java.io.File( filename )

if( file.exists() )
{
	def props = new java.util.Properties()
	props.load( new java.io.FileReader( file ))

	def propsMap = [:]

	propsMap.put("MainEdnpoint", props.get( "MainEdnpoint" ) )
	// add your subsequent property names here

	propsMap.each() {
		key, value ->
		checkAndSetProperty(key, value)
	}
}
else log.info( "Missing [" + filename + "]" )


public void checkAndSetProperty(def propName, def prop)
{
	// get test project object
	def project = testRunner.testCase.testSuite.getProject()

	if( prop != null && prop.trim().length() > 0 ) 
	{
		project.setPropertyValue( propName , prop )
	}
	else
	{
		log.info( "Property " + propName + " in configuration file has an empty value. Project property was cleared!!!!" )
		project.setPropertyValue( propName , "" )
	}
}

SoapUI: change paths for all REST service resources

Phew…
Finally I’ve managed to find a way to change all the resource paths in SoapUI’s REST service resources.

This solution can be helpful if you need to quickly add/change path parameter(s) to/in all service requests.

Quick explanation: Script below adds/removes a “?debug” or “&debug” param to all resource defined in your REST service.

import com.eviware.soapui.impl.rest.RestResource

def project = testRunner.testCase.testSuite.getProject()
String restServiceName = "your_rest_service_name"
List<RestResource> ops = project.getInterfaces()[restServiceName].getOperationList()

for (RestResource res : ops)
{
	res.each{
		op -> toggleDebug(op)
	}
}

public void toggleDebug(RestResource op)
{
	String path = op.getFullPath();

	if ( path.contains("&debug") || path.contains("?debug") )
	{
		if (path.contains("&debug"))
			{
				path = path.replaceAll("&debug","")
			}
		if (path.contains("/?debug"))
			{
				path = path.replaceAll("\\?debug","")
			}
		op.setPath(path)
		log.info "DEBUG mode DISABLED for operation: '" + op.getName() + "'. New path: "  + path
	}
	else
	{
		if (path.contains("?"))
			{
				path += "&debug"
			}
		else
			{
				path += "?debug"
			}
		op.setPath(path)
		log.info "DEBUG mode ENABLED for operation: '" + op.getName() + "'. New path: "  + path
	}
}