NOTE: This page contains information on standalone SoapUI Pro that has been replaced with ReadyAPI.
To try enhanced mock functionality, feel free to download a ReadyAPI trial from our website.
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:
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
Finally, for a MockResponse you can specify a “Response 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 ReadyAPI you can also define project-level event handlers to specify functionality that will be run for all MockServices in your project;
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:
// create XmlHolder for request content
def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
// get arguments and sum
def arg1 = Integer.parseInt( holder["//arg1"] )
def arg2 = Integer.parseInt( holder["//arg2"] )
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:
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:
// create XmlHolder for request content
def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
// get target testcase
def project = mockResponse.mockOperation.mockService.project
def testCase = project.testSuites["TestSuite 1"].testCases["TestCase 1"]
// set arguments as properties
testCase.setPropertyValue( "arg1", holder["//arg1"] )
testCase.setPropertyValue( "arg2", holder["//arg2"] )
// run testCase
def runner = testCase.run( new com.eviware.soapui.support.types.StringToObjectMap(), false )
if( runner.status.toString() == "FINISHED" )
requestContext.sum = testCase.getPropertyValue( "sum" )
else
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;
// get target request
def project = mockResponse.mockOperation.mockService.project
def request = project.interfaces["NewWebServicePortBinding"].operations["sum"].getRequestByName("Request 2")
// set request from incoming
request.requestContent = mockRequest.requestContent
// submit request asynchronously
request.submit( new com.eviware.soapui.impl.wsdl.WsdlSubmitContext( request ), false )
// save response to context
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;
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:
// create XmlHolder for request content
def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
// get arguments
def arg1 = holder["//arg1"]
def arg2 = holder["//arg2"]
if( !com.eviware.soapui.support.StringUtils.hasContent( arg1 ) ||
!com.eviware.soapui.support.StringUtils.hasContent( arg2 ))
return "Invalid Input Response"
try
{
Integer.parseInt( arg1 )
Integer.parseInt( arg2 )
}
catch( e )
{
return "Invalid Input Response"
}
// Select valid response randomly
def r = Math.random()
if( r < 0.33 )
return "Simple Response"
else if( r < 0.66 )
return "Call TestCase"
else
return "Call Request"
The containing MockOperation contains the used 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:
// add diagnostic
requestContext.freeMemory = Runtime.runtime.freeMemory()
...
This would make it available for property-expansion to all MockResponses defined for the MockOperation;
Which will return
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;
import groovy.sql.Sql
// open connection
def mockService = mockRunner.mockService
def sql = Sql.newInstance("jdbc:mysql://" + mockService.getPropertyValue( "dbHost" ) +
mockService.getPropertyValue( "dbName" ),
mockService.getPropertyValue( "dbUsername" ),
mockService.getPropertyValue( "dbPassword" ), "com.mysql.jdbc.Driver")
log.info "Succesfully connected to database"
// save to context
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;
// check for connection in context
if( context.dbConnection != null )
{
log.info "Closing database connection"
context.dbConnection.close()
}
So if we just start and stop the MockService we will see the following in the 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:
// create XmlHolder for request content
def holder = new com.eviware.soapui.support.XmlHolder( mockRequest.requestContent )
// get arguments and sum
def arg1 = Integer.parseInt( holder["//arg1"] )
def arg2 = Integer.parseInt( holder["//arg2"] )
// get connection and perform query
def sql = context.dbConnection
def row = sql.firstRow("select * from tb_saved_messages where arg1 = ? and arg2 = ?", [arg1, arg2])
// save result to property for response
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:
This will result in the entire message being written into the response.
7. Using files in a mock response
Using scripts, you can write information to local files or read information from them. To do that, create an instance of the File class by using the following code:
def file = new File(path)
Where path
is the fully qualified name of the file to load. If the file does not exist, it will be created when you write information to it.
To specify the path to the file located in the docroot folder specified in the mock option, use the following code:
def file = new File(mockRunner.mockService.docroot + \\foo.txt)
If you want to make the file path relative to the folder where your project is, use the following code:
// get the path to the folder where your project resides.
def projectPath = new File(mockOperation.mockService.project.path).parent
// Specify the file path that is relative to the project’s path.
def file = new File(projectPath+"\\foo.txt").
This approach is useful if you want to deploy your mock service as a WAR file.
8. 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:
// check for PUT
if( mockRequest.httpRequest.method == "PUT" )
{
def result = new com.eviware.soapui.impl.wsdl.mock.WsdlMockResult( mockRequest )
// build path
def path = mockRunner.mockService.docroot + File.separatorChar + mockRequest.httpRequest.queryString
path = path.replace( (char)'/', File.separatorChar )
// create File object and check if it doesnt already exists
def file = new File( path )
if( !file.exists() )
{
// create directories
if( path.lastIndexOf( ""+File.separatorChar ) > 1 )
new File( path.substring( 0, path.lastIndexOf( ""+File.separatorChar ))).mkdirs()
// write content
file << mockRequest.httpRequest.inputStream
mockRequest.httpResponse.status = 201
log.info "File written to [$file.absolutePath]"
}
else
{
mockRequest.httpResponse.status = 403
}
return result
}
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;
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:
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.
9. 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!