This modules provides the core Java APIs for creating and executing Test recipes which can then be executed locally or remotely as described in the Concepts document.
To simplify the creation of Test Recipes POJOs that get serialized to the corresponding JSON Recipes before execution this module provides a fluent API layer that attempts to provide a more “verbal” way of expressing Recipes through code. All the examples below will use this fluent API - but if you’re interested in the underlying POJO classes please have a look at the source or core javadocs. The java samples module contains examples using both the fluent and pojo approach.
Assert4j expresses tests as “recipes”. A test recipe is an ordered list of steps that are executed sequentially when the test is run. The easiest way to build a recipe is to use the TestRecipeBuilder class:
TestRecipe recipe = TestRecipeBuilder.buildRecipe( ...list of TestStepBuilder objects... );
// optionally add some more teststeps
recipe.addStep( ...another TestStepBuilder object... );
// execute the test!
RecipeExecutionResult result = RecipeExecutorBuilder.buildDefault().executeRecipe( recipe );
Test steps represent the actual actions performed during the execution of a test, the TestSteps class provides factory methods for creating TestStepBuilders for each supported test step type, as you will see below.
The TestSteps class provides convenience methods for the common HTTP Verbs:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/store/inventory" ),
POST( "http://petstore.swagger.io/v2/store/order" ),
DELETE( "http://petstore.swagger.io/v2//pet/{petId}"),
GET( "http://petstore.swagger.io/v2/pet/findByStatus" )
);
Both the DELETE and last GET in this example require parameter values - let’s add them:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/store/inventory" ),
POST( "http://petstore.swagger.io/v2/store/order" ),
DELETE( "http://petstore.swagger.io/v2//pet/{petId}").
withPathParameter( "petId", "1" )
,
GET( "http://petstore.swagger.io/v2/pet/findByStatus" ).
withQueryParameter( "status", "test")
);
If you need to add multiple parameters you can use
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/pet/findByStatus" ).
withParameters(
query( "status", "test"),
query("limit", "10")
)
);
Adding content to a POST or PUT is straight-forward:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
POST( "http://petstore.swagger.io/v2/store/order" ).
withMediaType( "application/json").
withRequestBody( ...some object that can be serialized to JSON... )
);
the built in serialization support json, yaml and xml media types.
If you need to add authentication to your request, then use one of the factory methods provided by the Authentications class.
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/pet/findByStatus" ).
withParameters(
query( "status", "test"),
query("limit", "10")
).
withAuthentication(
basic( "username", "password"),
oAuth2().
withAccessToken( ... )
)
);
If you’d like to attach a file to the body of a request instead of providing it as content as shown above, you can
use the withAttachments(...)
method together with the factory methods in the
Attachments class
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
POST( "http://petstore.swagger.io/v2/store/order" ).
withAttachments(
file( "request.json", "application/json" )
)
);
The built in SOAP support makes it super-easy to call SOAP Services. You will need to provide the underlying WSDL and details on which binding and operation to call. Filling out the actual message body can either be done using utility methods or manually by providing the request XML:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
soapRequest(new URL("http://www.webservicex.com/globalweather.asmx?WSDL"))
.forBinding("GlobalWeatherSoap11")
.forOperation("GetWeather")
.withParameter("CountryName", "Sweden")
.withPathParameter("//*:CityName", "Stockholm")
);
As you can see in this example we’re making a call to the GetOperation
method defined in the WSDL. The actual XML
request for this operation looks as follows:
<soapenv:Envelope
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:web="http://www.webserviceX.NET">
<soapenv:Header/>
<soapenv:Body>
<web:GetWeather>
<web:CityName>?</web:CityName>
<web:CountryName>?</web:CountryName>
</web:GetWeather>
</soapenv:Body>
</soapenv:Envelope>
Fortunately we never have to create any XML: we simply set the CountryName
parameter
using simple the corresponding element name ignoring namespaces. The CityName
parameter is set using an XPath
pointer, but it could equally have been done with the withParameter
method.
Transferring values from the response of one TestStep to the request of another is a very common task when creating multi-step tests. The Property Transfer TestStep handles this for you:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/pet/findByStatus?status=test" ).named("getPets"),
propertyTransfer(
fromPreviousResponse( "$.[0].id" ).toNextRequestProperty("petId")
),
GET( "http://petstore.swagger.io/v2/pet/{petId}" ).named( "getPet" ).
withAssertions(
statusCodes( 200 )
)
);
The example above uses XPath to extract the id
property of the first item in the response to the petId
path
parameter in the following request, using the fromPreviousResponse
and toNextRequest
convenience methods in the
PropertyTransferBuilder class.
Use the delayStep
to insert a delay between two TestSteps:
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/pet/findByStatus?status=test" ),
delayStep( 1000 ), // wait for one second...
GET( "http://petstore.swagger.io/v2/pet/(id}" )
);
Script TestSteps allow you to execute an arbitrary block of Groovy code as part of your test, for example
```java
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
GET( "http://petstore.swagger.io/v2/pet/findByStatus?status=test" ),
delayStep( 1000 ), // wait for one second...
groovyScriptStep( "System.out.println(\"Hello world!\")" ),
GET( "http://petstore.swagger.io/v2/pet/(id}" )
);
The specified code has access to the complete underlying object-model of the executing test, see https://www.soapui.org/scripting—properties/the-soapui-object-model.html and https://www.soapui.org/functional-testing/working-with-scripts.html for more info.
Test recipes can contain JDBC requests to interact with relational databases as part of a test. This can be valuable for either initializing data needed for a test, or for validating data that previous TestSteps are meant to create/modify. To use the JDBC TestStep make sure you have the corresponding JDBC driver in your classpath, then create a JdbcConnection object to the database which can be used to build the actual TestSteps:
JdbcConnection connection = jdbcConnection("org.mysql.Driver", "jdbc:mysql://localhost/mydb" );
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
connection.jdbcRequest("select * from some_table")
);
Internally the result returned from the query is converted to XML format, which you could for example use in combination with a property transfer:
JdbcConnection connection = jdbcConnection("org.mysql.Driver", "jdbc:mysql://localhost/mydb" );
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
connection.jdbcRequest("select id from pets").named( "SelectPetIds"),
propertyTransfer(
// use xpath to select first returned id
fromPreviousResponse( "//id[0]" ).toNextRequestProperty("petId")
),
GET( "http://petstore.swagger.io/v2/pet/{petId}" ).named( "getPet" ).
withAssertions(
statusCodes( 200 )
)
);
Assertions can be added to REST, SOAP and JDBC request teststeps via the withAssertions(...)
method to assert the content of their respective responses.
The Assertions
method contains factory methods that allow you to easily create any of the supported assertions, for example;
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
soapRequest(new URL("http://www.webservicex.com/globalweather.asmx?WSDL"))
.forBinding("GlobalWeatherSoap11")
.forOperation("GetWeather")
.withParameter("CountryName", "Sweden")
.withPathParameter("//*:CityName", "Stockholm")
.withAssertions(
validStatusCodes( 200 ),
notSoapFault(),
xPathContent( "//*:CityName", "Stockholm")
)
);
The following HTTP-specific assertions are available:
contentType( String contentType )
- asserts the content-type of the responseheaderExists( String header )
- asserts that the specified HTTP header exists in the responseheaderValue( String header, String value)
- asserts that the specified HTTP header has the specified value in the responsevalidStatusCodes( Integer... statusCodes)
- asserts that the response has one of the specified HTTP status codesinvalidStatusCodes( Integer... statusCodes)contentType
- asserts that the response does not have one of the specified HTTP status codesThese assertions are for validating the body content of a response:
contains(String token)
- asserts that the response body contains the specified tokennotContains(String token)
- asserts that the response body does not contain the specified tokenmatches( String regexToken)
- asserts that the response matches the specified regular expressionjson(String jsonPath, String expectedContent)
- asserts that the node identified by the specified jsonPath contains the expected valuejsonCount( String jsonPath, int count)
- asserts that the number of nodes selected by the specified jsonPath matches the specified countjsonExists( String jsonPath )
- asserts that a node exists at the specified JSON PathjsonNotExists( String jsonPath )
- asserts that a node does not exist at the specified JSON PathxPathContains( String xpath, String expectedValue )
- asserts that the XML node at the specified XPath contains the exptected valuexQueryContains( String xquery, String expectedValue )
- asserts that the value selected by the specified XQuery contains the exptected valueInternally the execution engine uses:
The following SOAP-specific assertions are available:
notSoapFault()
- assert that the SOAP response it not a SOAP FaultsoapFault()
- assert that the SOAP response is a SOAP Fault (for negative testing)schemaCompliance()
- asserts that the response is compliant with the XML Schema defined for the response messageThe following assertions are available for JDBC TestSteps:
jdbcRequestStatusOk()
- asserts that the JDBC statement was executed successfullyjdbcRequestTimeout( long timeout)
- asserts the JDBC response-time in millisecondsmaxResponseTime( long timeInMillis)
- assert the response time of the request to be within the specified timescript(String script)
- execute the specified Groovy script for asserting the response - see Using Script Assertions for some examplesProperties are a common concept in the SoapUI execution engine; all TestSteps expose properties and there is even a dedicated Properties TestStep for defining properties for a Test:
TestRecipe testRecipe = newTestRecipe(
properties(
property( "username", "..."),
property( "password", "...")
)
.named("Properties"),
...more TestSteps...
).buildTestRecipe();
Using defined properties is done via Property Expansion, for example, the above defined properties could be user for authentication:
TestRecipe testRecipe = newTestRecipe(
properties(
property( "username", "..."),
property( "password", "...")
)
.named("Properties"),
GET("..some endpoint..")
.withAuthentication(basic("${Properties#username}", "${Properties#password}")
)
)
).buildTestRecipe();
Of course you could simply define username/password as java variables when building the recipe; the advantage of using properties is related to the augmentation and reuse of recipes as described under recipe filters below.
Extractors allow you to easily extract values from a response message using XPath or JSONPath. For example you might want to use those values as input to other tests that are executed as part of an orchestrated end-to-end test suite:
String conversionRate;
TestRecipe recipe = TestRecipeBuilder.buildRecipe(
soapRequest(new URL("http://www.webservicex.com/CurrencyConvertor.asmx?wsdl"))
.forBinding("CurrencyConvertorSoap")
.forOperation("ConversionRate")
.named( "GetConversionRate")
.withParameter("FromCurrency", "USD")
.withParameter("ToCurrency", "SEK")
.withAssertions(
schemaCompliance(),
notSoapFault()
)
.withExtractors(
fromResponse( "//*:ConversionRateResult/text()", v -> {
conversionRate = v;
})
)
// execute recipe and use extracted conversionRate for something else
RecipeExecutionFacade.executeRecipe( recipe );
The Concepts document shows how to execute recipes using both the local and remote execution engines via the RecipeExecutionFacade class. Using the underlying RecipeExecutorBuilder makes it possible to augment execution using execution listeners and recipe filters as described below.
The actual Execution object available for the execution of a recipe is useful for querying asynchronous test execution status:
// use submitRecipe for async execution
Execution execution = RecipeExecutionFacade.submitRecipe( ... );
while( execution.getCurrentStatus() == RUNNING ){
// do something useful
}
System.out.println( "Test finished with status: " + execution.getCurrentStatus() );
ExecutionListeners get notified of specific events related to recipe execution - for example the provided ExecutionLogger writes all execution transaction logs as HAR files to a specified folder:
// build a local executor that logs executions to a logs folder
RecipeExecutor executor = new RecipeExecutorBuilder()
.withExecutionListener(new ExecutionLogger("logs"))
.buildLocal();
executor.executeRecipe( ... )
Since the ExecutionLogger
is commonly used the RecipeExecutorBuilder
actually has a
dedicated withExecutionLog( String logFolder)
method:
// build a local executor that logs executions to a logs folder
RecipeExecutor executor = new RecipeExecutorBuilder()
.withExecutionLog("logs")
.buildLocal();
executor.executeRecipe( ... )
RecipeFilters can be used to augment the underlying TestRecipe for a test before it is executed; for example it could add common authentication settings to all REST Requests, or common property values to all TestSteps of a certain type.
The included RecipeLogger filter logs all generated recipes to a specified folder - which can be useful for debugging/logging purposes or if you want to repurpose these recipes as LoadTests in LoadUI or API monitors using AlertSite
// build a local executor that logs json recipes to a logs folder
RecipeExecutor executor = new RecipeExecutorBuilder()
.withRecipeFilter(new RecipeLogger("logs"))
.buildLocal();
executor.executeRecipe( ... )
Similar to the ExecutionLogger
above, the RecipeExecutorBuilder
has a dedicated withRecipeLog( String logFolder)
method:
// build a local executor that logs executions to a logs folder
RecipeExecutor executor = new RecipeExecutorBuilder()
.withRecipeLog("logs")
.buildLocal();
executor.executeRecipe( ... )
When an execution finishes the Execution
object exposes a
RecipeExecutionResult
that gives access to a TestStepResult
for each executed TestStep. For example if you want to log the response content for all executed TestSteps you can do:
RecipeExecutionResult result = executor.executeRecipe(...);
for( TestStepResult testStepResult : result.getTestStepResults()){
System.out.println( "TestStep [" + testStepResult.getTestStepName() + "] returned [" + testStepResult.getResponseContent() + "]");
}