My SoapUI Thoughts on Effective Testing in SoapUI

Testing the HEAD Method

We have seen how to test two of the less-commonly tested HTTP methods OPTIONS and PATCH. I’d now like to complete the tests of the methods we identified in the service definition by implementing a test for the HEAD method.

Prerequisites

  • A test case configured to test the albums resource.

Goals

  • An understanding of how to implement tests for the HEAD method.

HEAD – making sure you’re up to date

The HEAD method allows you to confirm if your local copy of a resource matches the copy on the server, potentially enabling you to work with your copy without needing to get the full resource. We can use the albums test case to explore this functionality.

Let’s create a Props test step with enough properties to exercise the REST test steps without iterating over them via a script. The Props step should look as follows. PropsStep01 You can set up the POST test step to refer to these properties via the following JSON body:

{
	"userId": ${Props#userId},
	"title": "${Props#title}"
}

We will also need a test step to run the HEAD method. You should create a REST test step configured as follows, immediately after the GET test step. HEADMethod01 At this point, you can run the test case with the POST, GET and HEAD test steps. For assertions, it’s probably sufficient to create a HTTP status code assertion on each step.

According to www.w3.org, “The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information sent in response to a GET request.” This statement piques my interest as a tester given that SoapUI makes comparing the contents of requests very straightfoward.

Let’s validate this theory by extracting the headers from the GET request and comparing them to the headers in the HEAD request. To do this, script assertions are the tool of choice. Create a script assertion on the GET request containing the following code.

messageExchange.responseHeaders.each{ context."${it.key}" = it.value[ 0 ] }

This code makes use of the context variable. The context variable is a map of properties relating to the current test case. SoapUI exposes this map to any script elements in the test case. Its value to us here is that we’re not limited to the properties on the context map created by SoapUI; we can also add our own. The script in the assertion we’ve just created on the GET test step iterates over the response headers and creates an entry in the context map for each of them, setting the value to the contents of the GET response header.

Having captured each member of the GET response, we can now create a script assertion on the HEAD test step with the script code as follows.

1
2
3
4
5
6
messageExchange.responseHeaders.each {
	def conVal = context.get( it.key )
	if( it.value[0] != conVal ) {
		log.warn "HEAD: ${it.key} mismatch: ${it.value[0]} != $conVal from GET"
	}
}

This script iterates over the headers in the HEAD response and compares each value against what it found in the GET response. It creates a warning in the log for any differences it finds. With the assertions in place, you should now be able to run the test case and observe the log. I find that the HEAD response returns a different value for the CF-RAY parameter. If I were serious about testing the JsonPlaceholder API, I’d probably follow up on this difference.

This is all well and good, but I can hear you asking how the HEAD response allows you to avoid requesting a resource for which you may already have an up to date copy. Well, the answer is buried in the header parameters from both the GET and the HEAD responses. Each of these responses includes a parameter called Etag. This parameter is effectively a checksum which is calculated on the content of the resource. You can use a script assertion to store the Etag from a GET response in the test case context. When you subsequently run a HEAD command, you can compare the Etag in the HEAD response and only call another GET request if the Etag differs from what you saved earlier.

(For our purposes running against the JsonPlaceholder API the Etag won’t differ. But the picture changes completely if you’re running against a public API where other users may edit the resources you’re interested in.)

It’s safe to confine our test of the HEAD request to creating an assertion to check that the value of the Etag header matches the value we stored from the immediately preceding GET request. This can be done by appending the following statement to the HEAD request’s already existing script assertion.

assert context.Etag == messageExchange.responseHeaders[ "Etag" ][ 0 ]

There’s one last thing that’s worth looking at – and testing – when it comes to Etags. The GET method supports a specific request header that tells the request conditionally to return a resource’s body if it needs to. This header is called If-None-Match and takes an Etag as its value. If the server decides that the Etag in the request matches the specified resource, it returns a specific HTTP status: 304 Not Modified with no body. This lets you know that your copy of the resource is up-to-date. If the server finds that the Etag doesn’t match the resource, it returns the updated resource body (just as in a normal, unconditional GET request).

To test this as part of our test case, create a second GET request, after the HEAD request, and call it GET IF NONE MATCH. Configure it as follows. GETIFNONEMATCHMethod01 Note that the value of the If-None-Match header is specified as ${=context.Etag}. This notation – with the ‘=’ sign before the variable name – is an example of SoapUI’s dynamic property expansion. The value following the = is treated as a script in its own right rather than as a simple variable.

If you run the test case and examine the GET request, you should see that the raw request included the If-None-Match header with the value of the Etag returned by the preceding HEAD command, and that the response included a 304 Not Modified status with no resource body. By way of assertions, all you’d need to check is that the HTTP status is 304 and optionally that the Etag value in the response is the same as the one you passed in the request header.

You’ve probably perceived that this form of the GET command implies a second test condition: the return of an updated resource body in response to a stale Etag value. Unfortunately, the JsonPlaceholder API doesn’t allow us to test this. Even though we can issue PUT and PATCH commands and see the updated resource fields appear in the response, the resources aren’t actually updated on the server. We can’t pass a stale Etag in the GET headers and receive an updated resource.