Tuesday, November 27, 2012

Testing REST APIs with RESTClient and Selenium IDE

Update: You may also be interested in Testing Express.JS REST APIs with Mocha.

Tools used to test HTTP-based REST APIs include command-line utilities like curl and wget, as well as full-featured test frameworks like Mocha. Another interesting option is RESTClient, a plugin for Firefox that provides a user-friendly interface for testing REST APIs right from your browser.

Since RESTClient is a plugin, its made up of locally stored XML, HTML, CSS, JavaScript and RDF files that Mozilla refers to collectively as "chrome". It turns out that because RESTClient is built mainly with HTML, you can actually use Selenium IDE to control it and run through automated test cases.

Selenium IDE is used to run tests on regular web pages, but I've never seen it used to control another Firefox plugin before.  I gave it a try and discovered that it does this beautifully. Here's a picture of Selenium IDE driving RESTClient.

It's pretty cool to see Selenium IDE driving the RESTClient tool and knocking off all these test cases as it queries the API. I can run through an entire suite of tests, from setup to authentication, creating, retrieving, updating, and deleting resources, to final tear-down. I can drill down into any test cases that are failing and step through them to identify exactly what has gone wrong.  As a bonus, the nicely formatted output can be cut-and-pasted to create some pretty nice looking documentation in my client developer's guide.

Using RESTClient's Favourite Requests

One way to start using Selenium IDE to drive RESTClient is to set up a collection of "Favorite Requests" in RESTClient. Once your favourite requests have been configured, its relatively easy to create test cases in Selenium IDE to drive them.

This is not the best way to combine these tools, however.  It means you'll have to make sure your favourite requests are loaded into RESTClient before starting your Selenium test suite. Your requests will probably be hard-coded so they're not easy to share. And if you do want to run the tests on another system, you'll need to export your RESTClient faves as well as the Selenium tests. 

Driving RESTClient Programmatically

A better way is to drive RESTClient completely programmatically.  Instead of using a list of preconfigured requests in RESTClient, it would be really to just create the requests on-the-fly, programmatically from within your Selenium test suite. And this is completely possible.

Let's run through a few of the steps that you might need to accomplish this.

Selecting the Base URL

When you save your Selenium IDE test suite, the Base URL will define the starting point for your tests. This should be set to chrome://restclient/content/restclient.html

You can also launch RESTClient automatically from the first line of your first test case using the openWindow command:
openWindow | chrome://restclient/content/restclient.html

Making the API Server URL configurable

Depending on who's doing the testing, the server where the API is running will probably be different. If you want other people to be able to run your test suite against different servers you should at least make the hostname semi-configurable. You can do this by storing the hostname in a local variable that gets re-used for subsequent requests, like so:

store | darren.xyzzy.ca:3000 | server
echo | ${server}

That last line is just showing you how to access the stored "server" variable.

Test Preparation

I have a special URI that is available in development mode only, that allows initial set up of test data. This is the Selenese code I use to set up a call to that URI and confirm that test setup is complete:

click | css=i.request-method-icon.icon-chevron-down
click | link=GET
type | id=request-url | https://${server}/test/setup
click | id=request-button
click | link=Response Body 
waitForElementPresent | css=#response-body-raw > pre
waitForTextPresent | test setup complete

Most of those commands were created using the "Record" button in Selenium IDE; then I hand edited a few of them to get the right behaviour.  The most interesting line of the test case above is the "type" command. This enters text into the "request-url" input box, and it uses the ${server} variable to fill in the hostname:port of the API server.

Storing and using variable URL parameters

Just like we did with the "server" variable, you'll probably want to save and re-use other parameters that you get back from your REST API. Here's how you would extract an Account ID from a JSON response, for example after you issue a POST request to create a new account, and store it for use in subsequent requests.

... commands to query the API ...
click | Link=Response Body (Raw)
waitForElementPresent | css=#response-body-raw > pre
verifyTextPresent | Created account
storeText | css=#response-body-raw > pre | response
storeEval | JSON.parse(storedVars['response']).accountId | accountId
echo | ${accountId}

The interesting commands to note here are the "storeText" and "storeEval" commands. The storeText command stores the entire body of the response in a variable called "response".  The next command, storeEval, creates an object from the JSON response by retrieving the response text from storedVars['response'] and passing it into JSON.parse(). Then it selects the accountId property, and stores it in a local variable called, unsurprisingly, "accountId".  If subsequent request URLs are based on the accountId, we can use it just like the server parameter:

type | id=request-url | https://${server}/accounts/${accountId}/messages

If there are other properties of the JSON response body that we want to store for later use, we can use the same technique to get them, too.

Off to the races...

With these basic techniques you should be up and running RESTClient queries with Selenium IDE in no time.  Then you can launch your test suite and go grab a nice cup of coffee while your computer merrily churns out one test case after another and your browser magically performs the steps in sequence.  It's highly entertaining. But it has real practical value, too, as you're sure to find out when developing a client app and you need to troubleshoot the API communication.

Have fun!

The North Shore

Ishigaki, Okinawa, Japan

©2012 Darren DeRidder

Productivity and Note-taking

I told a friend of mine that I wasn't really happy with the amount of time that gets taken up by Slack and "communication and sched...