Monday, October 10, 2011

soapUI - Creating Dynamic MockServices

Although the static MockOperation and MockResponse model can give you some flexibility, it is often useful to add more dynamic behavior to your services allowing you to mock more complex service functionality. For example you might want to:

  • Transfer values from the request to the response, perhaps modifying them on the way
  • Read some data in a request and use its value to select which response to return
  • Read the response (or parts of it) from a database instead of having it statically in soapUI
  • Manually create a custom HTTP response
  • Etc...

Let’s have a look at how to achieve these in particular, but we’ll first give you a general overview of how you can make your MockServices more dynamic.

1. MockService Scripting Overview

A quick recap of the MockService model in soapUI: A MockService contains an arbitrary number of MockOperations, which each mock some WSDL operation in the containing project. These MockOperations can mock operations in different WSDL services, so a single MockService can mock any number of WSDLs to any extent. Each MockOperation contains an arbitrary number of MockResponse messages, i.e. messages that are returned to the client when the MockOperation is invoked. Exactly which message to return is decided by the MockOperation dispatch method, for which several are available; sequence, random, script, etc.

For the MockService itself there are number of scripting events available:

  • Start and Stop scripts; these are useful for opening or closing shared objects (for example collections or database connections) which can be made accessible to scripts “further down” in the mock object hierarchy.
  • OnRequest script; this is the main handler for simulating non-SOAP behavior (REST, etc); it is called before soapUI does any internal dispatching and has the possibility to return any kind of response to the client (thus bypassing the whole soapUI dispatch mechanism)
  • AfterRequest script: primarily intended for custom logging and reporting functionality

All these are available from the MockService window:

mockservice-scripts

For MockOperations one scripting possibility is available; selecting the “Script” option for a MockOperation Dispatch allows you to use script that decides which MockResponse to return to the client, an example of this will be further down

mockoperation-dispatch-script

Finally, for a MockResponse you can specify a “Response Script” :

mockresponse-script

This allows for response-specific script functionality that will be executed before the containing MockResponse message is created and returned to the client; this is the primary place to generate dynamic response content.

In soapUI Pro you can also define project-level event handlers to specify functionality the will be run for all MockServices in your project;

mock-eventhandlers

The available Mock-Related handlers are:

  • MockRunListener.onMockResult – corresponds to the MockService AfterRequest script
  • MockRunListener.onMockRunerStart – corresponds to the MockService Start script
  • MockRunListener.onMockRunerStop – corresponds to the MockService Stop script
  • MockRunListener.onMockRequest – corresponds to the MockService OnRequest Script

2. Mock Handler Objects

A number of objects are commonly available in most scripts; here comes a quick overview with links to their corresponding javadoc:

  • context – used for storing MockService-wide objects, for example database connections, security tokens, etc
  • requestContext – used for storing Mock Request-wide objects, for example dynamic content to be inserted into the response message
  • mockRequest – an object corresponding to the actual request made to the MockService. Through this object you can get hold of the incoming message, its headers, etc. Also the underlying HttpRequest and HttpResponse objects are available for “low-level” processing
  • mockResult – an object encapsulating the result of the dispatched request, this is available in the MockService.afterRequest script.
  • mockResponse/mockOperation – objects available at the corresponding levels for accessing the underlying configuration and functionality
  • mockRunner – the object corresponding to the execution of the MockService; this gives you access to previous mock results, etc

Ok, now let’s dive in to see how these can be used!

3. Transferring values from the request to the response

This is straightforward scripting done at the MockResponse level; the script uses properties of the mockRequest object to get hold of the incoming request, extracts the desired values via XPath and then writes the created result to a requestContext property:

1.// create XmlHolder for request content
2.def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
3.
4.// get arguments and sum
5.def arg1 = Integer.parseInt( holder["//arg1"] )
6.def arg2 = Integer.parseInt( holder["//arg2"] )
7.
8.requestContext.sum = arg1 + arg2

As you can see the extracted value is assigned to a “sum” property in the requestContext (which is specific for this request). This is then used in the response with standard property-expansion:

mockresponse-complete

The panel to the left shows the last request sent to our MockOperation.

4. Creating the response from the result of a TestCase

This is a bit more elaborate; we’ll create a MockResponse script that first executes a soapUI TestCase and uses its outcome to populate the MockResponse message:

01.// create XmlHolder for request content
02.def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
03.
04.// get target testcase
05.def project = mockResponse.mockOperation.mockService.project
06.def testCase = project.testSuites["TestSuite 1"].testCases["TestCase 1"]
07.
08.// set arguments as properties
09.testCase.setPropertyValue( "arg1", holder["//arg1"] )
10.testCase.setPropertyValue( "arg2", holder["//arg2"] )
11.
12.// run testCase
13.def runner = testCase.run( new com.eviware.soapui.support.types.StringToObjectMap(), false )
14.if( runner.status.toString() == "FINISHED" )
15.requestContext.sum = testCase.getPropertyValue( "sum" )
16.else
17.requestContext.sum = "Error: " + runner.reason

The script first gets hold of the target TestCase to run, sets some properties with the argument values and then executes it. If all goes well the result is read from another TestCase property and returned via the same mechanism as above, otherwise an error is shown instead.

If we just would have wanted to execute a single request and return that requests response (turning soapUI into a “proxy”), we could do this as follows;

01.// get target request
02.def project = mockResponse.mockOperation.mockService.project
03.def request = project.interfaces["NewWebServicePortBinding"].operations["sum"].getRequestByName("Request 2")
04.
05.// set request from incoming
06.request.requestContent = mockRequest.requestContent
07.
08.// submit request asynchronously
09.request.submit( new com.eviware.soapui.impl.wsdl.WsdlSubmitContext( request ), false )
10.
11.// save response to context
12.requestContext.responseMessage = request.responseContentAsXml

Here we assign the entire response to a requestContext property; the actual MockResponse message is just a property-expansion of this property;

mockresponse-complete

5. Selecting a response based on the request

This script is specified at the MockOperation level and uses the same code as above to extract the input values from the incoming request. Based on some validations it returns the name of the MockResponse to return to the client. The script is as follows:

01.// create XmlHolder for request content
02.def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
03.
04.// get arguments
05.def arg1 = holder["//arg1"]
06.def arg2 = holder["//arg2"]
07.
08.if( !com.eviware.soapui.support.StringUtils.hasContent( arg1 ) ||
09.!com.eviware.soapui.support.StringUtils.hasContent( arg2 ))
10.return "Invalid Input Response"
11.
12.try
13.{
14.Integer.parseInt( arg1 )
15.Integer.parseInt( arg2 )
16.}
17.catch( e )
18.{
19.return "Invalid Input Response"
20.}
21.
22.// Select valid response randomly
23.def r = Math.random()
24.if( r < 0.33 )
25.return "Simple Response"
26.else if( r < 0.66 )
27.return "Call TestCase"
28.else
29.return "Call Request"

The containing MockOperation contains the used MockResponses:

selectable-mockresponses

The requestContext variable available in the script is of course the same as we saw in the previous example, allowing you to pass values from the dispatch script to the MockResponse. For example we can add a “freeMemory” property to the requestContext which we add to all MockResponses for diagnostic purposes:

1.// add diagnostic
2.requestContext.freeMemory = Runtime.runtime.freeMemory()
3....

This would make it available for property-expansion to all MockResponses defined for the MockOperation;

freememory-in-mockresponse

Which will return

freememory-result

to the client.

6. Read the response from a database

This one requires a bit more work as we need to set up and close a database connection which we can use in our scripts. This is best done in the MockService start script as follows;

01.import groovy.sql.Sql
02.
03.// open connection
04.def mockService = mockRunner.mockService
05.
06.def sql = Sql.newInstance("jdbc:mysql://" + mockService.getPropertyValue( "dbHost" ) +
07.mockService.getPropertyValue( "dbName" ),
08.mockService.getPropertyValue( "dbUsername" ),
09.mockService.getPropertyValue( "dbPassword" ), "com.mysql.jdbc.Driver")
10.
11.log.info "Succesfully connected to database"
12.
13.// save to context
14.context.dbConnection = sql

Here we set up a connection (using groovys’ built in database support) to the configured database (configuration parameters are taken from the MockService properties) which is then saved in the context, making it available to all scripts further down. A corresponding script to close the connection when the MockService stops, is of course required;

1.// check for connection in context
2.if( context.dbConnection != null )
3.{
4.log.info "Closing database connection"
5.context.dbConnection.close()
6.}

So if we just start and stop the MockService we will see the following in the log:

database-script-log

Perfect! In this specific and simplistic example I have a table containing the whole SOAP response to be returned to the client; a value in the request is used to look up which response to return. I’ve chosen to do all this logic in a single MockResponse script:

01.// create XmlHolder for request content
02.def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
03.
04.// get arguments and sum
05.def arg1 = Integer.parseInt( holder["//arg1"] )
06.def arg2 = Integer.parseInt( holder["//arg2"] )
07.
08.// get connection and perform query
09.def sql = context.dbConnection
10.def row = sql.firstRow("select * from tb_saved_messages where arg1 = ? and arg2 = ?", [arg1, arg2])
11.
12.// save result to property for response
13.requestContext.responseMessage = row.responseMessage

Pretty straight forward:

  • Extract the required values from the request
  • Get the db connection from the context
  • Perform your query
  • Write the response to a requestContext property

The response message itself only has the property-expansion as content:

database-mockresponse

This will result in the entire message being written into the response.

7. Creating a custom response

This is currently the only way to mock a REST or more complex HTTP service with soapUI; the OnRequest script on the MockService level gives you direct access to the underlying HttpRequest and HttpResponse objects allowing you to create whatever response you would like to. All you need to be sure of is that the script returns a MockResult object which tells soapUI to stop processing the request. So for example if we want to handle a PUT request we could do the following:

01.// check for PUT
02.if( mockRequest.httpRequest.method == "PUT" )
03.{
04.def result = new com.eviware.soapui.impl.wsdl.mock.WsdlMockResult( mockRequest )
05.
06.// build path
07.def path = mockRunner.mockService.docroot + File.separatorChar + mockRequest.httpRequest.queryString
08.path = path.replace( (char)'/', File.separatorChar )
09.
10.// create File object and check if it doesnt already exists
11.def file = new File( path )
12.if( !file.exists() )
13.{
14.// create directories
15.if( path.lastIndexOf( ""+File.separatorChar ) > 1 )
16.new File( path.substring( 0, path.lastIndexOf( ""+File.separatorChar ))).mkdirs()
17.
18.// write content
19.file << mockRequest.httpRequest.inputStream
20.mockRequest.httpResponse.status = 201
21.
22.log.info "File written to [$file.absolutePath]"
23.}
24.else
25.{
26.mockRequest.httpResponse.status = 403
27.}
28.
29.return result
30.}

As you can see the script writes the request body to the path specified by the URL using the docroot set in the MockService options dialog as root;

mockservice-options

Necessary directories are created and the appropriate status code is returned. Calling for example http://localhost:8299/?/some/path/file.dat with HTTP PUT and a message body will result in the file being created. You can use an HTTP TestRequest Step in soapUI to do this:

httpputtestrequest

And now since the file has been created in your MockServices’ docroot you can just use your web browser and specify http://localhost:8299/some/path/file.dat which will retrieve the corresponding file.

8. Final Words

That's it! The scripting possibilities for MockServices allow you to easily create dynamic and “life-like” MockServices (we even have users that actually implement their services in soapUI and deploy them with the deploy-as-war functionality), this document should have given you a better understanding of the possibilities. Good Luck!

Last Updated on Friday, 06 May 2011 11:04

15 comments:

Anonymous said...

It is perfect time to make some plans for the future and it's time to be happy. I have read this post and if I could I want to suggest you few interesting things or suggestions. Perhaps you could write next articles referring to this article. I want to read even more things about it!

Feel free to surf to my site - White Kidney Beans

Anonymous said...

Hey exceptional website! Does running a blog like this take
a massive amount work? I've very little understanding of programming but I was hoping to start my own blog soon. Anyway, if you have any ideas or tips for new blog owners please share. I know this is off topic however I simply had to ask. Many thanks!

My blog post Sapphire Electronic Cigarette

Anonymous said...

Pretty nice post. I just stumbled upon your blog and wanted to say that I've truly enjoyed browsing your blog posts. After all I will be subscribing to your rss feed and I hope you write again soon!

Visit my web blog Male enhancement

Anonymous said...

Do you have a spam problem on this site; I also am a blogger, and I was curious about your situation;
many of us have developed some nice practices and we are looking
to exchange methods with others, please shoot me an e-mail if interested.


Look into my blog: Buy lift serum

Anonymous said...

I just like the valuable information you supply in your articles.
I will bookmark your blog and check once more here regularly.

I'm fairly certain I'll learn many new stuff proper right here!
Best of luck for the following!

Look at my web site: Xtrasize Male Enhancement

Anonymous said...

Someone essentially help to make seriously posts I would state.
That is the first time I frequented your web page and thus far?
I amazed with the analysis you made to create this actual submit extraordinary.
Wonderful task!

Also visit my blog post :: Beta Force Muscle Solution

Anonymous said...

Wow that was odd. I just wrote an incredibly long comment but after I clicked submit my comment didn't show up. Grrrr... well I'm not writing all that over again.
Anyhow, just wanted to say superb blog!

my web site ... Garcinia Cambogia Extract

Anonymous said...

I simply could not depart your website prior to suggesting that I really enjoyed the standard information an individual provide in your visitors?
Is going to be back continuously in order to investigate cross-check new posts

Look at my site - Muscle Maximizer Reviews

Anonymous said...

Thanks for one's marvelous posting! I definitely enjoyed reading it, you
will be a great author. I will be sure to bookmark your blog and will come back later on. I want to encourage you to continue your great job, have a nice evening!


Look at my weblog ijser.info

gokceng said...

Just copy/paste from SoapUI's web site. Show your work please.

Borigor said...

Nice post!
Wonder, if there easy way to read into Response template with placeholders, fill placeholders variables with autogenerated values and parse it before assigning to Response context?

Anonymous said...

ecigarette, smokeless cigarettes, smokeless cigarettes, e cigarette, e cigarette, electronic cigarette brands

Unknown said...

www0626

adidas soccer shoes
ugg outlet
michael kors outlet
canada goose outlet
ray ban sunglasses
ray ban sunglasses
rockets jerseys
canada goose outlet
true religion jeans
warriors jerseys





yanmaneee said...

kate spade handbags
michael kors outlet online
nike air max 270
nike cortez men
air max 2019
christian louboutin outlet
adidas zx flux
ralph lauren uk
nfl jerseys
cheap nfl jerseys

Anonymous said...

hermes outlet online
golden goose outlet
off white
jordan 12
supreme
off white outlet
bape t shirt
supreme outlet
yeezy 700
kyrie shoes