Webservice testing with JMeter: Passing data from a response to another request
JMeter is great for functional and performance testing of many things, including web services (and to my surprise also LDAP). It also provides means for extracting data from a response and passing them to a subsequent request, which is exactly what I needed. There is already a good tutorial on testing a WS with JMeter, so I won't repeat the basic setup here. The steps are:
To extract the value of the element <saba:certificate>:
Well, that's it!
The trick is:
- Create a webservice (WS) test plan, as described in the tutorial (in my case it contains two WS calls)
- Add the User Defined Variables config element to the test plan and define there a variable for transferring the response data
- Add an XPath Extractor Post Processor to the first WS call to extract the value of interest into the user defined variable (beware namespaces!)
- Add a BeanShell Pre Processor to the second call, which will replace a placeholder in the WS call's XML data with the value of that variable
About the webservice
I needed to test a web service, which requires its client to call first its authenticate method, which returns an authentication token called 'certificate', which is then used in subsequent requests.A basic implementation
0. Setup
Download JMeter 2.3.4 and two dependencies, Java Mail API (mail.jar) and JavaBeans Activation Framework (activation.jar), necessary for the JMeter's webservice sampler. Put the JARs in JMeter's lib/ folder.1. Create a webservice (WS) test plan, as described in the tutorial (in my case it contains two WS calls)
Well, follow the utorial :-). Then duplicate the webservice call sampler, call the first one WS: Authenticate with Saba and the other one WS: PF - Update employees.2. Add the User Defined Variables config element to the test plan and define there a variable for transferring the response data
We will need a variable to hold the data that we want to transfer from the 1st response to subsequent request. Therefore open the test plan, right-click on Thread Group > Add > Config Element > User Defined Variables. Add there a variable named sabaCertificate. You can leave its Value empty.3. Add an XPath Extractor Post Processor to the first WS call to extract the value of interest into the user defined variable
Now we will extract the "certificate" data from the first response. The response may look like this (I used Eclipse' TCP Monitor to capture the SOAP communication):
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<soapenv:Body>
<saba:certificate xmlns:saba="http://www.saba.com/xml/infoservices">31323930326436636637635</saba:certificate>
</soapenv:Body>
</soapenv:Envelope>
To extract the value of the element <saba:certificate>:
- Right-click on the first WS call (WS: Authenticate with Saba) and Add > Post Processors > XPath Extractor
- For the Reference Name, type sabaCertificate (the user variable we've created earlier)
- For the XPath query, type //*[local-name()='certificate']/text()
- Problem with namespaces: Beware that JMeter 2.3.4 supports only namespaces declared on the root element and thus the XPath query //saba:certificate wouldn't work. The documentation for XPath Extractor's attribute "Use Namespace?" provides a workaround based on using the functions local-name() and namespace-uri() to match the local tag name and the URI associated with its namespace, which I've partly used.
- You can test your XPath for example in the Allans Online XPath Tester
4. Add a BeanShell Pre Processor to the second call, which will replace a placeholder in the WS call's XML data with the value of that variable
Now we need to get the "certificate" into the subsequent web service request. I have put the placeholder "#sabaCertificate#" into the SOPA request, at the place where the actual authentication token shall be. Now we will arrange for its replacement with the actual value:- Right-click on the second WS call (WS: PF - Update employees) and Add > Pre Processors > BeanShell PreProcessor (BeanShell is a scripting language with Java syntax and is included in JMeter)
- Type in the following script (notice that sampler is a variable provided by JMeter and refers to the parent WS call; check JavaDoc for details on the WebServiceSampler):
Case 1: SOAP request specified directly in the attribute Soap/XML-RPC Data
import org.apache.jmeter.protocol.http.sampler.WebServiceSampler;
WebServiceSampler wsSampler = (WebServiceSampler) sampler;
String requestWithCertif = wsSampler.getXmlData().replaceFirst("#sabaCertificate#", vars.get("sabaCertificate"));
wsSampler.setXmlData(requestWithCertif);
Case 2: The SOAP request is read from a file (attribute File with SOAP XML Data)
If the request data is read from a file then it's a bit more complex because we need to load its content.
import org.apache.jmeter.protocol.http.sampler.WebServiceSampler;
import java.io.*;
WebServiceSampler wsSampler = (WebServiceSampler) sampler;
BufferedReader xmlReader = new BufferedReader( new InputStreamReader(
new FileInputStream(wsSampler.getXmlFile())
, java.nio.charset.Charset.forName("UTF-8")
));
StringBuffer xmlData = new StringBuffer();
String line;
while( (line = xmlReader.readLine()) != null) { xmlData.append(line).append('\n'); }
String requestWithCertif = xmlData.toString().replaceFirst("#sabaCertificate#", vars.get("sabaCertificate"));
wsSampler.setXmlData(requestWithCertif);
wsSampler.setXmlFile("") ; // a file would override the data
// print("XML set: " + requestWithCertif); // print to the console JMeter was started from
Well, that's it!
Going advanced: Reading requests from several files
The approach descibed above makes it possible to send a request based on a single file. But what if we want to send a different data with each repetition of the test, e.g. to negate effects of caching? Well, there is a couple of ways to achieve that. I've chosen the most flexible one, though absolutely not the easiest one to implement.The trick is:
- Create a BeanShell Sampler. The sampler will list all files in a particular directory and store their paths into a numbered variables (G_updateEmployeesWsRequestFile_1 etc., must start with 1), which will be then used by a ForEach Controller.
- Put all the test elements from the basic test plan under a ForEach Controller , which follows the BeanShell Sampler. Configure it to use the variables generated by the BeanShell Sampler and store the current file name in the variable G_updateEmployeesWsRequestFile.
- In the webservice request element, replace the content of the Filename field with a reference to that variable: ${G_updateEmployeesWsRequestFile}
The BeanShell Sampler "Generate WS request file names"
import java.io.*;
print("Generating files...");
log.info("BeanShell Sampler: Generating request file names...");
File requestsDir = new File("/tmp/wsRequests");
String[] requestFiles = requestsDir.list();
for(int i=0; i<requestFiles.length; ++i) {
String varName = "G_updateEmployeesWsRequestFile_" + (i+1);
vars.put(
varName
, requestsDir.getAbsolutePath() + File.separatorChar + requestFiles[i]
);
// print("var created: " + varName + "=" + vars.get(varName));
}
log.info("BeanShell Sampler: FINISHED generating request file names from dir " +
requestsDir + "; files are: " + java.util.Arrays.asList(requestFiles));
return "soap input files generated";
The ForEach Controller "ForEach request file"
The controller's configuration is simple:- Input variable prefix: G_updateEmployeesWsRequestFile
- Output variable name: G_updateEmployeesWsRequestFile
- Add "_" before before number: [x] (checked)
Summary
We've parametrized the test by a set of files with SOAP requests that are read from a folder and supplied sequentially to the test thanks to the ForEach Controller.Resources
- The final Basic JMeter Test Plan
- The Advanced JMeter Test Plan (SOAP requests read from files in a folder)