mocha-qa
Version:
A convenience helper for testing promises using the mocha library
305 lines (264 loc) • 6.96 kB
JavaScript
/* jshint node:true */
/* global it, before, beforeEach, after, afterEach, window */
;
var _ = require('lodash');
//
// Argument handling has been derived from the way
// angular $injector is parsing function arguments:
// https://github.com/angular/angular.js/blob/master/src/auto/injector.js#L71
//
var FN_ARGS = /^function\s*[^\(]*\(\s*([^\)]*)\)/m;
var FN_ARG_SPLIT = /,/;
var STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg;
function callbackRequiresDone (fnc) {
var fncText = fnc.toString().replace(STRIP_COMMENTS, '');
var matches = fncText.match(FN_ARGS);
if (!matches) {
return false;
}
var args = matches[1].replace(/ /g, '').split(FN_ARG_SPLIT);
return _.contains(args, 'done');
}
function PromiseRejectedError (message) {
this.name = 'PromiseRejectedError';
this.message = message;
}
PromiseRejectedError.prototype = Error.prototype;
function PromiseResolvedError (message) {
this.name = 'PromiseResolvedError';
this.message = message;
}
PromiseResolvedError.prototype = Error.prototype;
function UnsupportedEnvironmentError (message) {
this.name = 'UnsupportedEnvironmentError';
this.message = message;
}
UnsupportedEnvironmentError.prototype = Error.prototype;
/**
*
* Attaches then / catch callbacks to a given promise.
*
* Will call done() on promise resolution and done(error)
* on rejection.
*
*/
function attachPromiseHandlers (fnc, done) {
function resolve () {
done();
}
function reject (error) {
if (!error) {
error = new PromiseRejectedError('Test failed. Expected promise to be resolved (No error provided).');
}
done(error);
}
try {
var promise = fnc.apply(null, [done]);
if (promise && _.isFunction(promise.then) && _.isFunction(promise.catch)) {
return promise
.then(resolve)
.catch(reject);
}
else if (promise && _.isFunction(promise.then)) {
return promise
.then(resolve, reject);
}
resolve();
}
catch (error) {
reject(error);
}
}
/**
*
* Attaches then / catch callbacks to a given promise.
*
* Will call done(error) on promise resolution and
* done() on rejection, thus resolving the test whenever
* the promise gets rejected.
*
*/
function attachErrorHandlers (fnc, done) {
function resolve () {
done();
}
function reject (error) {
if (!_.isError(error)) {
error = new PromiseResolvedError('Test failed. Expected promise to be rejected (No error provided).');
}
done(error);
}
try {
var promise = fnc.apply(null, [done]);
if (promise && _.isFunction(promise.then) && _.isFunction(promise.catch)) {
return promise
.then(reject)
.catch(resolve);
}
else if (promise && _.isFunction(promise.then)) {
return promise
.then(reject, resolve);
}
reject();
}
catch (error) {
resolve();
}
}
var doneFnc;
var itFnc = it;
var beforeFnc = before;
var beforeEachFnc = beforeEach;
var afterFnc = after;
var afterEachFnc = afterEach;
/**
*
* Promise wrapper around the mocha.js library.
*
* Adds a number of convenience methods for handling
* promises as results of tests.
*
*/
module.exports = {
_test: function setDone(fnc, done) {
doneFnc = done;
itFnc = fnc;
beforeFnc = fnc;
afterFnc = fnc;
beforeEachFnc = fnc;
afterEachFnc = fnc;
},
/**
*
* Promise variant of the default it-test.
*
* Handles a returned promise and
* calls done() to conclude the test in case of
* a positive resolution.
*
* Fails the the test if the promise gets rejected
* or any of the assertions within the test run fails.
*
*/
it: function promiseIt (description, fnc) {
if (callbackRequiresDone(fnc)) {
return itFnc(description, fnc);
}
return itFnc(description, function (done) {
return attachPromiseHandlers(fnc, doneFnc || done);
});
},
/**
*
* Convenience variant for testing promise rejections.
*
* Handles a returned promise and
* calls done() to conclude the test in case of
* a negative resolution, i.e., the catch block
* of the promise handling chain is triggered.
*
* Fails the the test if the promise gets resolved.
*
*/
catchIt: function catchIt (description, fnc) {
return itFnc(description, function (done) {
return attachErrorHandlers(fnc, doneFnc || done);
});
},
/**
*
* Promise variant of the default before-hook.
*
* Handles a returned promise and
* calls done() to conclude the hook in case of
* a positive resolution.
*
* Fails the the hook if the promise gets rejected.
*
*/
before: function promiseBefore(fnc) {
if (callbackRequiresDone(fnc)) {
return beforeFnc(fnc);
}
return beforeFnc(function (done) {
return attachPromiseHandlers(fnc, doneFnc || done);
});
},
/**
*
* Promise variant of the default beforeEach-hook.
*
* Handles a returned promise and
* calls done() to conclude the hook in case of
* a positive resolution.
*
* Fails the the hook if the promise gets rejected.
*
*/
beforeEach: function promiseBeforeEach(fnc) {
if (callbackRequiresDone(fnc)) {
return beforeEachFnc(fnc);
}
return beforeEachFnc(function (done) {
return attachPromiseHandlers(fnc, doneFnc || done);
});
},
/**
*
* Promise variant of the default after-hook.
*
* Handles a returned promise and
* calls done() to conclude the hook in case of
* a positive resolution.
*
* Fails the the hook if the promise gets rejected.
*
*/
after: function promiseAfter(fnc) {
if (callbackRequiresDone(fnc)) {
return afterFnc(fnc);
}
return afterFnc(function (done) {
return attachPromiseHandlers(fnc, doneFnc || done);
});
},
/**
*
* Promise variant of the default afterEach-hook.
*
* Handles a returned promise and
* calls done() to conclude the hook in case of
* a positive resolution.
*
* Fails the the hook if the promise gets rejected.
*
*/
afterEach: function promiseAfterEach(fnc) {
if (callbackRequiresDone(fnc)) {
return afterEachFnc(fnc);
}
return afterEachFnc(function (done) {
return attachPromiseHandlers(fnc, doneFnc || done);
});
},
/**
*
* Adds all promise hooks as global variables
*
*/
global: function makeGlobal () {
var global = typeof window === 'undefined' ? GLOBAL : window;
if (global) {
global.it = this.it;
global.catchIt = this.catchIt;
global.before = this.before;
global.beforeEach = this.beforeEach;
global.after = this.after;
global.afterEach = this.afterEach;
}
else {
throw new UnsupportedEnvironmentError('Could not recognize global process. It seems as if your current environment setup does not support globalizing.');
}
return this;
}
};