Skip to content


Simple REST API Testing Setup using Vows.js, Tobi, and Node.js

I’m learning so many new things at BrightTag! Among them, I’m helping develop a new service in node.js. After piecing together a few articles online about using node for asynchronous testing of REST services, I thought I’d share the testing setup I’d settled on, in case it can help you. The setup is written for vowsjs and can easily be tailored to your situation. I’ve tried to make heavy use of asynchronization, macros, and contextualized requests. In the case below, I was integration-testing an oauth security middleware (simplified here for, well, simplicity).

var vows = require('vows'),
    assert = require('assert'),
    should = require('should'),
    tobi = require('tobi'),
    http = require('http');
 
var HOST = 'localhost',
    PORT = 8087;
 
var suite = vows.describe('My API Security'),
    browser = tobi.createBrowser(PORT, HOST);
 
var client = {
  get: function(path, header, callback) {
    browser.get(path, { headers: header }, callback)
  },
  post: function(path, data, header, callback) {
    browser.post(path, { body: JSON.stringify(data), headers: header }, callback)
  }
}
 
//
//Send a request and check the response status.
//
function respondsWith(res) {
  var context = {
    topic: function () {
      // Get the current context's name, such as "POST / access_token"
      // and split it at the spaces.
      var req    = this.context.name.split(/ +/), // ["POST", "/", "access_token"]
          method = req[0].toLowerCase(),          // "post"
          path   = req[1];                        // "/"
          token  = req[2];                        // "access_token"
 
      var header = { 'Authorization': 'Bearer ' + token };
 
      // Perform the contextual client request,
      // with the above method and path.
      client[method](path, header, this.callback);
    }
  };
 
  // Create and assign the vows to the context.
 
  context['should respond with a ' + status + ' '
         + http.STATUS_CODES[status]] = assertStatus(res.status);
 
  errorMessage = (error == undefined) ? 'out errors' : ' an "'+error+'" error';
  context['should respond with' + errorMessage] = assertHeaders(res.error, res.errorDescription);
 
  return context;
}
 
function assertStatus(code) {
  return function (res, $) {
    res.should.have.status(code)
  }
}
 
function assertHeaders(error, errorDescription) {
  return function (res, $) {
    if (error || errorDescription) {
      res.should.have.header('www-authenticate');
      header = oauth.parseAuthenticationHeader(res.headers['www-authenticate']);
      if (error) error.should.equal(header.error);
      if (errorDescription) errorDescription.should.equal(header.error_description);
    } else {
      res.should.not.have.property('www-authenticate');
    }
  }
}
 
suite.addBatch({
  'GET /resource/1 authorized': respondsWith({ status: 200 }),
  'GET /resource/1 un@vth0riz3d': respondsWith({ status: 401, error: 'invalid_token', errorDescription: 'The access token is invalid' }),
  'GET /resource/1 expired': respondsWith({ status: 401, error: 'invalid_token', errorDescription: 'The access token is expired' }),
  'GET /resource/1 insufficient': respondsWith({ status: 403, error: 'insufficient_scope' })
})
 
suite.export(module)

This pattern is nice because adding new tests is a real piece of cake now that we have the contextualized request macro, and this macro pattern is easy to tailor to your situation. It also demonstrates how to set two contexts, which was left “to the reader” on the vows guide. You can even imagine adding more vows and assertions, such as one that checks the body content, into the macro, as I’ve done in the real case.

Can you refactor or modify this to make it cleaner still? Let me know in the comments.

Posted in Tutorials.


2 Responses

Stay in touch with the conversation, subscribe to the RSS feed for comments on this post.

  1. Craig Hooper says

    YES!

    • codyaray says

      Node.js is hot stuff, Mr. Hooper. :)



Some HTML is OK

or, reply to this post via trackback.

 



Log in here!