API Testing with Frisby

I recently started work on server software to act as the backend of a Mac app I am working on. This is a pretty simple REST API with no HTML components so I only need to test endpoints. Generally I would test such a thing manually with curl, but this time I wanted to automate the testing so I looked around for test frameworks that I could use.

My requirements were that the test framework should be independent of the server technology and easy to set up and use. I want to be independent of the server technology so that I can swap the server out completely - say, from Ruby on Rails to Sinatra, or WebObjects :) - and the tests will still be valid.

Frisby.js

After some digging around I settled on using Frisby.js for this. Frisby is a testing framework built on node.js and Jasmine - neither of which I have used much. So, this’ll be fun I thought!

Installation

First step is to install Node and NPM, which was pretty simple, then I followed the Frisby install process which, again, was easy. Good start.

Creating tests

To get started testing we created a file to hold the tests, the convention here is to name the file something like user_spec.js - the _spec is important.

Tests are described by creating an HTTP request, setting expectations about the response then tossing the frisby. Cute!

Here is an example from my code requesting the creation of a new user:

 1 // user_spec.js
 2 var frisby = require('frisby');
 3 var helpers = require('./test_helpers');
 4 
 5 var test_user_number = helpers.randomInt(1000000, 9999999).toString()
 6 var test_user = "test_" + test_user_number
 7 var test_email = "test_" + test_user_number + "@bazscott.com"
 8 
 9 frisby.create('Create a new test user')
10   .post('http://localhost:3000/api/v1/user',
11   	{ "user" : { "name" : test_user, "email" : test_email } },
12   	{ json: true },
13   	{ headers: { 'Content-Type': 'application/json' }}
14   )
15   .expectStatus(201)
16   .expectHeader('Content-Type', 'application/json; charset=utf-8')
17   .expectHeaderContains('content-type', 'application/json')
18   // .inspectJSON()
19   .expectJSON({
20     user: {
21 	  name: test_user,
22       email: test_email
23     }
24   })
25   .expectJSONTypes({
26     user: {
27       name: String,
28       email: String
29     },
30     api_key: String
31   })
32 .toss();
33 
34 // test_helpers.js
35 module.exports = {
36 	randomInt: function randomInt(min, max) {
37 		return Math.floor(Math.random() * (max - min + 1)) + min;
38 	}
39 };

The code above, breaks down like this:

  • Lines 5-7: Set up some test data.
  • Lines 9-14: Create a POST request to the endpoint under test, passing the test data in JSON format. At this point we can include headers if required.
  • Lines 15-17: Set up expectations for the response. In this case we expect a `201` status code and JSON content to be returned.
  • Lines 18-31: The `.inspectJSON()` call can be useful in testing your tests, so to speak, it simply outputs the JSON to the console when run locally. The other expectations describe the data we expect to be returned and the types of data.
  • Line 32: At the end we run the test using the `.toss()` command.
  • Lines 34-39: Helper methods can be stored in an external file.

Now for the good part

After line 31 in user_spec.js and before we toss the frisby we can add the following code which will allow us to capture some output from the POST request and use it in subsequent requests. In this case we want to capture the api_key that was produced for the user and use this in subsequent headers.

We do that using the afterJSON function, then use the globalSetup function to set the headers.

In the example below you will see this happening and then a DELETE request is sent, in reality you would probably do some more useful testing.

 1   // user_spec.js
 2   .afterJSON(function (res) {
 3 	// console.log("DEBUG :: " + res.api_key); // TESTING
 4 	// Include API KEY in the header of all future requests
 5 	frisby.globalSetup({
 6 	  request: {
 7 		headers: {
 8 		  'X-API-KEY': res.api_key,
 9 		  'Content-Type': 'application/json'
10 		}
11 	  }
12 	});
13 
14 	frisby.create('DELETE test user')
15 	  .delete('http://localhost:3000/api/v1/user')
16 	  .expectStatus(204)
17 	.toss();
18 
19   }) // afterJSON

Easy isn’t it? I hope you find this a useful starting point.