Skip to content

Instantly share code, notes, and snippets.

@atheken
Created September 26, 2010 13:00
Show Gist options
  • Save atheken/597908 to your computer and use it in GitHub Desktop.
Save atheken/597908 to your computer and use it in GitHub Desktop.
JavaScript Unit Test Creator
/*
via: http://javascriptweblog.wordpress.com/2010/09/20/auto-generating-javascript-unit-tests/?utm_source=feedburner&utm_medium=feed&utm_campaign=Feed:+JavascriptJavascript+(JavaScript,+JavaScript)
*/
var tester = {
testing: [],
console: window.console || {log: function(a) {window.status = a}, warn: alert},
defineBaseTests: function() {
this.baseTestBefore = [this.argumentsDefinedTest, this.thisBindingTest];
this.baseTestAfter = [this.returnTest];
},
testAll: function(root, options) {
if (!root || (root == (function(){return this})())) {
alert("too many functions to iterate\nspecify a non-global root"); return;
}
options = options || {};
var testChildren = function(root, path) {
var nextPath, recurse;
root && (root.assignedName = path);
for (var key in root) {
var obj = root[key];
var objType = typeof obj;
nextPath = path + (path && ".") + key;
if ((objType == 'function')) {
root[key].assignedName = obj.name || nextPath;
root[key].owners = [root];
root[key] = tester.test(obj, {debug: options.debug});
this.console.log('writing tests for ' + nextPath);
tester.testing.push({root:root, key:key});
} else {
recurse =
objType == "object" &&
obj != root &&
('.' + path + '.').indexOf('.' + key + '.') == -1 &&
root.hasOwnProperty(key);
recurse && testChildren(obj, nextPath);
}
}
}
testChildren(root || window, options.path || "");
},
untestAll : function() {
for (var i = 0, thisTest; thisTest = this.testing[i]; i++) {
thisTest.root[thisTest.key] = thisTest.root[thisTest.key].original;
}
this.testing = [];
},
test: function(fn, options) {
options = options || {};
var fnName = fn.assignedName || fn.name;
if (fn.original && (fn.assignedName != fn.original.assignedName)) {
//there are multiple references to this function, concat owners
[].push.apply(fn.original.owners,fn.owners);
}
//if this is already a test revert to original function and create new test
fn = fn.original || fn;
var getTestFunction = function(fn, testBefore, testAfter) {
return function() {
for (var i=0; i<testBefore.length; i++) {
(msg = testBefore[i](fn, arguments, this)) && diagnostic(msg);
}
var result = fn.apply(this,arguments);
for (var i=0; i<testAfter.length; i++) {
(msg = testAfter[i](fn, result)) && diagnostic(msg);
}
return result;
}
}
var diagnostic = function(msg) {
this.console.warn(fnName + ": " + msg);
if (options.debug) { debugger }
};
//build test suite
!this.baseTestBefore && this.defineBaseTests();
var testBefore = this.baseTestBefore.slice();
var testAfter = this.baseTestAfter.slice();
options.customTestBefore && testBefore.push(options.customTestBefore);
options.customTestAfter && testAfter.push(options.customTestAfter);
var testFunction = getTestFunction(fn, testBefore, testAfter);
testFunction.original = fn;
for (var prop in fn) {
testFunction[prop] = fn[prop];
}
return testFunction;
},
//LIBRARY OF TESTS
// 'before' tests take original function, arguments and thisBinding
argumentsCountTest: function(fn, args, thisBinding) {
return (args.length < fn.length) &&
args.length + " arguments supplied, " + fn.length + " defined";
},
argumentsDefinedTest: function(fn, args, thisBinding) {
for (var i=0; args[i] !== undefined; i++);
return (i != args.length) && "undefined arguments were passed";
},
thisBindingTest: function(fn, args, thisBinding) {
//("thisBinding.constructor" checks for constructor functions)
return !(thisBinding.constructor && (thisBinding.constructor == fn)) &&
fn.owners && (!~fn.owners.indexOf(thisBinding)) &&
"this bound to " + thisBinding + ", but function owner is " +
fn.owners.pluck('assignedName').join(" OR ");
},
// 'after' tests take original function, and result of function call
returnTest: function(fn, result) {
return (fn.toString().match(/[\s]+return\s[^\s]+;\n[\s]?}$/) && (result === undefined)) &&
"return value expected";
},
resultIsNumber: function(fn, result) {
if (typeof result != 'number') {
return "expecting number for result, got a " + typeof result;
}
}
}
//UTILITIES
Array.prototype.pluck = function(prop) {
for (var i = 0, member, result = []; member = this[i]; i++) {
result.push(member[prop] || member);
}
return result;
}
//new function needed for non ECMA 5 compliant browsers only
Array.prototype.indexOf = [].indexOf || function(member) {
for (var i = 0, len = this.length; i<len; i++) {
if (this[i] === member) {
return i;
}
}
return -1;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment