Sorting XML with Groovy

Manipulating the Order of Elements in a JMeter Response

When testing a SOAP webservice, one of the things you want to test is what you’re getting back from the server. Do the elements in the response match the expectations? When testing your backend services with JMeter there are several ways to do this. The most common of which is using Post Processor XPath Extractors together with XPath Assertions.

But what if you get back a complex xml structure with a list of items, for which the order in the xml is not set? Sure, then there is XPath, but the XPath query becomes quite complex and one might wonder: wouldn’t there be a more straightforward approach to sorting xml? This is where sorting xml with Groovy comes into play.

Scenario

We want to check the database contents for a specific test scenario against the data returned in the xml response of the SOAP service, but the order in which the xml elements are returned is not guaranteed. Now, we don’t want to add sorting in our code just for the sake of testing. Therefore, our approach is:

  1. In our JMeter test we add a JDBC Request Sampler to extract some records based on the search criteria for our test scenario. Here, we sort the result on a specific and unique field so the order of our query results is guaranteed.
  2. We sort the record elements in the xml response in the same order.
  3. In a ForEach Logic Controller we can then loop through the records found from the database in step 1, and match its contents against the xml elements from step 2.

JMeter JDBC Request

To compare the xml response against the database contents, we run a query and sort the result based on the unique eventId . The order of the fields in the select statement matches the order of the variable names. The result of the query is a list of 14 records sorted on eventId .

As you can see on the right, in case multiple records are returned, JMeter creates separate variables with a sequence number, based on the variable names we provided. The idea is to sort the xml response also on eventId  so we will get the same sequence for the record elements in the xml:

DB Result

JMeter Response Extraction

After having called your SOAP service, using an HTTP Request Sampler, you will get back some xml, possibly with a SOAP-Header and SOAP-Body. You can extract part of the xml that you want to assert, using a Post Processor XPath Extractor. E.g. to extract the response body we could do something like:

Post Processor XPath Extractor

Resulting in the following xml, which we store in a variable named searchResponse:

<searchResponse>
    <info>
        <status>Succes</status>
        <code>4104</code>
        <description>Results found</description>
        <version>3</version>
    </info>
    <result>
        <numberOfResults>14</numberOfResults>
        <records>
            <record>
                <general>
                    <id>20294</id>
                    <type>Payment</type>
                    <eventId>Event-P014</eventId>
                </general>
                <status>
                    <statusCode>Rejected</statusCode>
                    <timestamp>2021-06-21T09:45:53.092</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20282</id>
                    <type>Payment</type>
                    <eventId>Event-P002</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:45.022</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20283</id>
                    <type>Payment</type>
                    <eventId>Event-P003</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:45.364</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20284</id>
                    <type>Payment</type>
                    <eventId>Event-P004</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:45.749</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20285</id>
                    <type>Payment</type>
                    <eventId>Event-P005</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:46.383</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20286</id>
                    <type>Payment</type>
                    <eventId>Event-P006</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:46.740</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20287</id>
                    <type>Payment</type>
                    <eventId>Event-P007</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:46.981</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20288</id>
                    <type>Payment</type>
                    <eventId>Event-P008</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:47.671</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20289</id>
                    <type>Payment</type>
                    <eventId>Event-P009</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:48.232</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20290</id>
                    <type>Payment</type>
                    <eventId>Event-P010</eventId>
                </general>
                <status>
                    <statusCode>Blocked</statusCode>
                    <timestamp>2021-06-21T09:45:48.472</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20291</id>
                    <type>Payment</type>
                    <eventId>Event-P011</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:49.299</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20292</id>
                    <type>Payment</type>
                    <eventId>Event-P012</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:52.090</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20293</id>
                    <type>Payment</type>
                    <eventId>Event-P013</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:52.634</timestamp>
                </status>
            </record>
            <record>
                <general>
                    <id>20281</id>
                    <type>Payment</type>
                    <eventId>Event-P001</eventId>
                </general>
                <status>
                    <statusCode>Activated</statusCode>
                    <timestamp>2021-06-21T09:45:42.921</timestamp>
                </status>
            </record>
        </records>
    </result>
</searchResponse>

As you can see, we get back 14 record elements, but these are not sorted. If we want to sort on eventId  the first record element should become the last, and the last record element should become the first. So before we can do the assertions, we first need to manipulate the searchResponse  a little.

The Groovy Script

JMeter’s scripting can be hard, because there’s no real scripting editor with code completion (and no automatic imports) in JMeter. Therefore I prefer to test things out in something like IntelliJ whenever possible. Below I created a Groovy class with a main method to first try to get the result we want to see.

	import groovy.xml.XmlParser
	import groovy.xml.XmlUtil
	
	class XmlSortBlog {
	
	    static void main(String[] args) {
	
	        def response = '''
	            <XML here>
	        '''
	
	        def rootNode = new XmlParser().parseText(response.trim())
	
	        def recordsSorted = rootNode.result.records.record.sort(true) { it.general.eventId.text() }
	
	        rootNode.result.records.head().children().clear()
	
	        recordsSorted.each { rootNode.result.records.head().append(it) }
	
	        def responseSorted = XmlUtil.serialize(rootNode)
	
	        println responseSorted
	    }
	}

Line 1

Depending on the version of Groovy: in older versions the import was groovy.util.XmlParser . Since version 3.0.0 this has become deprecated and groovy.xml.XmlParser  should be used. It may depend on the JMeter version which one you should use. With JMeter 5.1.1, the newer import is not working, so the old import should still be used.

Line 2

XmlUtil  contains helper methods used for pretty printing XML content and other XML related utilities.

Line 9

Copy/paste the xml here that you want to test, for example the xml mentioned in the previous section.

Line 12

First we trim the response removing any leading and trailing spaces from the xml string. If there are leading spaces in front of the xml declaration, this may lead to a parse error.

Then we create an instance of XmlParser  and call the parseText  method with the xml string, which gives us a Node  object so we can easily navigate through the DOM structure.

Line 14

The rootNode  starts with <searchResponse> . Here we want to reorder the record elements. Its access path: rootNode.result.records.record . On this we can call the sort method. We want to sort on the text that is between the eventId begin and end tags, within the general element.

Passing the attribute true  to the sort  method mutates the underlying list, which reorders the child nodes.

Line 16

Here we jump to the records  element and remove all its child nodes. In other words: we remove all record  elements.

Line 18

Here we loop through the sorted result from line 14 and we append the sorted records one by one to the records element.

Line 20

We serialize the rootNode (<searchResponse> ) back to a readable xml string.

Line 22

Print out the sorted result on the console for a visual check. Or even better, write a test for it.

The Groovy Script within a JMeter JSR223 Sampler

Now that we have a working script, we can place this in a JSR223 Sampler. Make sure to select groovy as the language. Be aware that this shouldn’t be a JSR223 PreProcessor or JSR223 PostProcessor, but a JSR223 Sampler. In the JMeter sampler we need to add a few extra things:

The Groovy Script within a JMeter JSR223 Sampler

Line 1

Since I’m using JMeter 5.1.1, I had to use the old import of groovy.util.XmlParser .

Line 5

Retrieve the searchResponse  JMeter variable we created earlier in the section ‘JMeter Response Extraction’.

Line 24

Here we set the sorted xml back as response data so this can be used for extracting the individual record elements. This will only work when using a JSR223 Sampler (and not when using a JSR223 PreProcessor or PostProcessor).

Extracting the Sorted Record Elements

Now that the xml response has been sorted, a Post Processor XPath Extractor can be added under the JSR223 Sampler as described above in the section ‘The Groovy Script within a JMeter JSR223 Sampler’:

The Groovy Script within a JMeter JSR223 Sampler

Resulting in JMeter variables being created as mentioned before:

Finally, the order from the database result is the same as that of the elements in the xml response, and assertions can be added based on the sequences.

Groovy? Groovy!

Resources

• Groovy: Processing XML
• Groovy: XmlSlurper
• Scripting JMeter Assertions in Groovy – A Tutorial
• Use Groovy to Sort XML File

Leave a Reply

Your email address will not be published. Required fields are marked *