Saturday, 23 April 2011

SoapUI aide memoire and groovy scripting (on RTM)

A braindump (not a tutorial) on SoapUI including use of Groovy to script parameter generation!
To whet pre-jump appetites, the SoapUI primary structure:
  • Project - houses...
    • Methods - Create these including parameters that need populating later (I'm doing REST atm)
    • TestSuite - One can only script within a test suite/case so we'll need one
      • TestCases -  one for each separable chunks of work.
        Lots of choices but I've mostly used...
        • GroovyScript - code to generate values (like hashes)
        • PropertyTranfer - Used to copy values between scripts and calls' parameters (REST calls, etc)
        • Test Request (REST/SOAP/HTTP/JDBC/AMF!) to make actual calls
    • Setup Script and TearDown Script can be placed on TestSuite, TestCase, etc to also house the scripting.  (but I've generally found scripts more handy.)
    • Project-wide utility methods can only be defined with a SoapUI Pro license (IIUC).  Without this, I've ended-up with some copy-and-paste re-use :-(  Shame there's no sliding scale from Community to Pro!
Full details after the break and please note this post's a WIP -- perhaps forever!  ('90s webpage, anyone? ;-) )
So, the rest of this post is in the context of my RTM Android Sharer but I hope it'll be sufficiently general to be helpful.  Why was I using SoapUI when I'm coding Android?  Well, I wanted to confirm my understanding of the RememberTheMilk API -- specifically, to test:
  1. connectivity
  2. authentication
  3. creating a task with...
    1. a URL
    2. a note
    3. a set of tags
  4. retrieving a list of tags to auto-suggest for the list above (later)
As a long-time basic user of  SoapUI, I've been aware that there's Groovy scripting but never found need to use in anger.  However, the RTM authentication API signs each call by MD5-hashing a shared-secret, all parameters and values into a parameter called "api_sig".  Obviously, this needs some scripting!  So, new project steps...
  1. Create the basic SoapUI REST structure + methods
    (may add more detail if people interested / I fear I'll forget)
  2. Create a test suite
  3. Create TestCases for separable chunks of work.
    (logging in, creating a task, etc)
  4. Add Properties to Suite/TestCase/... to act as global variables to move data through (bletch!)
    (e.g. auth_token needs passing to all calls once received from previous call so stash now and retrieve as described in PropertyTransfer section below)
  5. Add GroovyScript items to do some operations -- in my case the api_sig generation.
  6. Add REST Test Requests with both populated and empty parameters
  7. Add PropertyTranfer to move data between steps (see below)
  8. To ensure everything's going well, throw in some assertions on the results.
    (such as XPath /rsp/@stat to confirm the root "rsp" element has a "stat" attribute whose value "ok" one places in the "Expected Result" field.)
Although we've all used SoapUI's capabilities in the past, I still find myself re-learning parts on first use so below follow some aide memoir notes on this delightfully hairy yak :-)


Wow, are these powerful!
As mentioned above, I found it handy to create holder-slots on the Test Step (/ Case / Suite depending how widely something was useful) and transferring values from a previous Response to these for future use.  Not sure whether that's best practise but it definitely works :-)

Data transfer slightly different depending upon source...
  • XML Response results:
    • XQuery is optional, XPath is used if not selected and that's plenty powerful!
    • Retrieve text values with /rsp/timeline
      (will retrieve from <rsp><timeline>DATA</...></...>)
    • Retrieve attribute values with /rsp/@stat
      (will retrieve from <rsp stat="DATA"></...>)
    • Some simple mouse/eyeball errors ...
      1. Error:
        net.sf.saxon.trans.XPathException: XPath syntax error at char 9 in {/rsp/list@id}:
            Unexpected token "@" beyond end of expression
        Cause: Missing "/" before "@".  Should be /rsp/list/@id
      2. Error:
        Missing match for Source XQuery [/rsp/list/@id]
        Cause: Erroneously ticked XQuery -- you're using XPath but selected to use XQuery.  Make up your mind ;-)
      3. Error:
        Cause: Likely both (1) and (2) = Missing "/" and erroneously ticked XQuery.
  • Script results:
    • return from your Groovy script and select script result to pull out.  It's how I did the MD5 hashing above.
    • To transfer data into a script, stash the value in a property and retrieve.  Rather than using navigation via testSuite or testCase, it's usually easier to use the context's expander since it's uniformly available in all scripts (so doesn't require copy-and-paste editing if you lack SoapUI Pro for global libraries).  E.g:
      • To retrieve a custom property "api_key" from the TestSuite,
      • Rather than ...
        def api_key ="api_key".value;
        (which will only work when you have testSuite available which might require navigating to it from other variables) 
      • ... use ...
        def api_key = context.expand('${#TestSuite#api_key}');
Great tips to have a look at:
  • The docs are a little discontinuous so here're my favourite janky links:
  • Note XmlHolder may not seem to be giving a powerful enough return type (e.g. String!).  Instead of using its getNodeValue(xpath) to get Xml Node then doing details, usually better to make a smarter XPath query that returns exactly what you wanted to test.  E.g.
    • To find the value attribute of the api input field in the loginform,
    • Once one has the XmlHolder by..
      import; "messageExchange: "+ messageExchange;
      def holder = new XmlHolder(messageExchange.responseContentAsXml);
      assert null != holder;
    • ... rather than...
      def apiNodeValueAttr = holder.getDomNode("//form[@id='loginform']/input[@name='api']/@value");
      assert null != apiNodeValueAttr; "apiNodeValueAttr: "+ apiNodeValueAttr;
      def value = apiNodeValueAttr.getNodeValue();
      assert null != value; "api node value: "+ value;
    • ... use...
      def apiNodeValue = holder["//form[@id='loginform']/input[@name='api']/@value"];
      assert null != apiNodeValue; "apiNodeValue: "+ apiNodeValue;
  • XPath wildcards don't work in 3.6.1 but allegedly fixed in 3.6.2.  Tried but doesn't seem to be as of 2011/04/23.
  • Unverified: Disabling Assertions doesn't seem to be saved?
  • ...(maybe done / maybe more to follow as I discover)...
Whew, lots of SoapUI braindumpage! Hope you found some wheat hidden in my chaff.
p.s. Can sanitize and post the project if it'd be handy.  (no, you're not having my RTM API key :-P )

No comments:

Post a Comment