Speck

Test tools on top of Vows

assert/macros

lib/assert/macros.js

Module imports

var vows    = require( 'vows' );
var assert  = require( 'assert' );

Module exports

exports.isBuffer = isBuffer;

assert extensions

assert.isBuffer = isBuffer;

Buffer instance assertion

  • api: public

  • type: Function

  • param: Object actual Object to assert against

  • param: String message Custom assertion message

Examples

 assert.isBuffer( new( Buffer ), '{actual} is not a Buffer' );

function isBuffer ( actual, message ) {
  if ( !( actual instanceof Buffer ) ) {
    assert.instanceOf( actual, Buffer, message || 'expected {actual} to be an instance of Buffer', 'instanceof', assert.instanceOf );
  }
}

klient

lib/klient.js

Module imprts

var _       = require( 'underscore' )._;
var util    = require( 'util' );
var http    = require( 'http' );
var events  = require( 'events' );
var Buffer  = require('buffer').Buffer;

Module exports

exports.Klient = Klient;

Client Abstraction for HTTPServer/API tests

  • api: public

  • type: Function

  • param: options

  • return: Klient

function Klient ( config ) {
  events.EventEmitter.call( this );

  this.options = _.extend( {}, this.defaults, config || {} );
  this.server = this.options.server;
  this.deferred = [];
  this.started = false;
  this.pending = false;
}

util.inherits( Klient, events.EventEmitter );

Defaults

  • api: private

  • type: Object

  • param: String host

  • param: Number port

Klient.prototype.defaults = {
  host: 'localhost',
  port: 8081,
  path: '/'
};

Handles queued requests

  • api: private

  • type: Function

Klient.prototype.processDeferred = function () {
  var self = this;
  var deferred = self.deferred;

  process.nextTick( function () {
    _( deferred.length ).times( function () {
      self.request( deferred.shift() );
    });
  });
};

Wrapper for request method to return a promise

  • api: private

  • type: Function

  • param: Object options

  • return: EventEmitter

Klient.prototype.promisedRequest = function ( options ) {
  var self = this;
  var promise = new( events.EventEmitter );

  options.promise = promise;

  self.request( options );

  return promise;
};

Binding of test server

  • api: public

  • type: Function

Klient.prototype.listen = function ( callback ) {
  var self = this;
  var server = self.server;
  var host = self.options.host;
  var port = self.options.port;
  var callback;

  cb = function () {
    self.pending = false;
    self.started = true;
    self.processDeferred();

    if ( _.isFunction( callback ) ) {
      callback();
    }
  };

  self.pending = true;

  server.listen( port, host, cb );
};

Closes test server

  • api: public

  • type: Function

Klient.prototype.close = function() {
  this.server.close();
};

Main request logic with lazy binding of test server

  • api: public

  • type: Function

  • param: Object options

Klient.prototype.request = function ( options ) {
  var self = this;
  var server = self.server;
  var promise = options.promise;
  var req;

  if ( server && ( !server.fd || !self.started ) ) {
    self.deferred.push( options );

    if( !self.started && !self.pending ) {
      self.listen();
    }

    return;
  }

  options = _.extend( {}, self.options, options );
  req = http.request( options );

  process.nextTick( function () {
    req.on( 'response', function ( res ) {
      var body = new( Buffer )(0);

      res.on('data', function(chunk) {
        var buffer = new( Buffer )( body.length + chunk.length );

        body.copy( buffer );
        chunk.copy( buffer, body.length );

        body = buffer;
      });

      res.on('end', function() {
        if ( res.headers['content-type'] === 'application/json' ) {
          body = JSON.parse( body.toString() );
        }

        res.body = body;

        promise.emit( 'success', res );
      });
    }).on( 'error', function ( err ) {
      promise.emit( 'error', err );
    });

    if ( _.isArray( options.data ) ) {
      _.each( options.data, function (data) {
        req.write( data );
      });
    } else if ( options.data ) {
      req.write( options.data );
    }

    req.end()
  });
};

GET convenience method

  • api: public

  • type: Function

  • return: EventEmitter

Klient.prototype.get = function ( options ) {
  _.extend( options, { method: 'get' } );

  return this.promisedRequest( options );
}

POST convenience method

  • api: public

  • type: Function

  • return: EventEmitter

Klient.prototype.post = function ( options ) {
  _.extend( options, { method: 'post' } );

  return this.promisedRequest( options );
}

PUT convenience method

  • api: public

  • type: Function

  • return: EventEmitter

Klient.prototype.put = function ( options ) {
  _.extend( options, { method: 'put' } );

  return this.promisedRequest( options );
}

DELETE convenience method

  • api: public

  • type: Function

  • return: EventEmitter

Klient.prototype.del = function ( options ) {
  _.extend( options, { method: 'delete' } );

  return this.promisedRequest( options );
}

makros

lib/makros.js

Module imports

var _       = require( 'underscore' )._;
var assert  = require( 'assert' );

Module exports

exports.body    = body;
exports.header  = header;
exports.request = request;
exports.status  = status;

Deep equality response body test

  • api: public

  • type: Function

  • param: String | Object value to test against

  • return: Function

function body ( value ) {
  return function ( err, res ) {
    assert.ifError( err );
    assert.deepEqual( res.body, value );
  }
}

Arbitary header test macro

  • api: public

  • type: Function

  • param: String name of the header to check for

  • param: String value to test against

  • return: Function

function header ( name, value ) {
  return function ( err, res ) {
    assert.ifError( err );
    assert.equal( res.headers[name], value );
  }
}

statusCode test macro

  • api: public

  • type: Function

  • param: Number code the statusCode should match

  • return: Function

function status ( code ) {
  return function ( err, res ) {
    assert.ifError( err );
    assert.equal( res.statusCode, code );
  }
}

Request/Response cycyle macro

  • api: public

  • type: Function

  • param: Object request Options for the http request

  • param: Object response Criteria the response should match

  • param: Object vows Arbitary vows to be executed

  • return: Function

function request ( request, response, vows ) {
  var context;
  
  context = {
    topic: function ( client ) {
      var nameSplit = this.context.name.split(/ +/);
      var method    = request.method || nameSplit[0].toLowerCase();
      var path      = request.path || nameSplit[1];

      request = _.extend({
        method: method,
        path: path
      }, request );

      return client[( method === 'delete' ) ? 'del' : method]( request );
    }
  };

  if ( response.status ) {
    context['should respond with *' + response.status + '*'] = status( response.status );
  }

  if ( response.headers ) {
    _.each( response.headers, function ( value, key ) {
      context['should return ' + key + ': *' + value + '*'] = header( key, value );
    });
  }

  if ( response.body ) {
    context['should send correct *body*'] = body( response.body );
  }

  if ( vows ) {
    _.each( vows, function ( vow, name ) {
      context[name] = vow;
    })
  }

  return context;
}