Created
September 4, 2014 17:57
-
-
Save ssaunders/64d8c20e5506e3ccd74b to your computer and use it in GitHub Desktop.
Expose private variables for unit testing in JavaScript
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* The code below takes a constructor and exposes its private functions | |
Useful for unit testing (not general use). An example usage is below it. | |
Access to the private functions available through the _privMems property. */ | |
/* Original credit goes to Rob Gravelle | |
http://www.htmlgoodies.com/beyond/javascript/accessing-private-functions-in-javascript.html */ | |
var Reflection = {}; | |
Reflection.createExposedInstance = function(objectConstructor, args) | |
{ | |
// get the functions as a string | |
var objectAsString = objectConstructor.toString(); | |
var aPrivateFunctions = objectAsString.match(/function\s*?(\w.*?)\(/g); | |
// To expose the private functions, we create | |
// a new function that goes trough the functions string | |
// we could have done all string parsing in this class and | |
// only associate the functions directly with string | |
// manipulation here and not inside the new class, | |
// but then we would have to expose the functions as string | |
// in the code, which could lead to problems in the eval since | |
// string might have semicolons, line breaks etc. | |
//INITIAL CODE | |
/* var funcString = "new (" | |
+ objectAsString.substring(0, objectAsString.length - 1) | |
+ ";" | |
+ "this._privMems = {};\n" | |
+ "this._initPrivates = function(pf) {" | |
+ " this._privMems = {};" | |
+ " for (var i = 0, ii = pf.length; i < ii; i++)" | |
+ " {" | |
+ " var fn = pf[i].replace(/(function\\s+)/, '').replace('(', '');" | |
+ " try { " | |
+ " this._privMems[fn] = eval(fn);" | |
+ " } catch (e) {" | |
+ " if (e.name == 'ReferenceError') { continue; }" | |
+ " else { throw e; }" | |
+ " }" | |
+ " }" | |
+ "}" | |
+ "\n\n})()"; | |
var instance = eval(funcString); | |
instance._initPrivates(aPrivateFunctions); | |
*/ | |
// SECOND TRY, WORKS WITH PROTOTYPE CHAIN | |
/* var funcString = objectAsString.substring(0, objectAsString.length - 1) | |
+ ";" | |
+ "this.prototype=Object.create(objectConstructor.prototype);\n" | |
+ "this._privMems = {};\n" | |
+ "this._initPrivates = function(pf) {" | |
+ " this._privMems = {};" | |
+ " for (var i = 0, ii = pf.length; i < ii; i++)" | |
+ " {" | |
+ " var fn = pf[i].replace(/(function\\s+)/, '').replace('(', '');" | |
+ " try { " | |
+ " this._privMems[fn] = eval(fn);" | |
+ " } catch (e) {" | |
+ " if (e.name == 'ReferenceError') { continue; }" | |
+ " else { throw e; }" | |
+ " }" | |
+ " }" | |
+ "}" | |
+ "\n\n}"; | |
eval("var OtherClass = "+funcString); | |
OtherClass.prototype = Object.create(objectConstructor.prototype); | |
var instance = new OtherClass(); | |
instance._initPrivates(aPrivateFunctions); | |
// delete the initiation functions | |
delete instance._initPrivates; | |
return instance; | |
*/ | |
// THIRD TRY, WORKS WITH PROTOYPES AND ARGUMENTS | |
var instance = Object.create(objectConstructor.prototype); | |
var otherstring = objectAsString.substring(0, objectAsString.length - 1) | |
+ ";" | |
+ "this.prototype=Object.create(objectConstructor.prototype);\n" | |
+ "this._privMems = {};\n" | |
+ "this._initPrivates = function(pf) {" | |
+ " this._privMems = {};" | |
+ " for (var i = 0, ii = pf.length; i < ii; i++)" | |
+ " {" | |
+ " var fn = pf[i].replace(/(function\\s+)/, '').replace('(', '');" | |
+ " try { " | |
+ " this._privMems[fn] = eval(fn);" | |
+ " } catch (e) {" | |
+ " if (e.name == 'ReferenceError') { continue; }" | |
+ " else { throw e; }" | |
+ " }" | |
+ " }" | |
+ "}" | |
+ "\n\n}"; | |
eval("var OtherClass = "+otherstring); | |
OtherClass.apply(instance, args); | |
instance._initPrivates(aPrivateFunctions); | |
// delete the initiation functions | |
delete instance._initPrivates; | |
return instance; | |
}; | |
/**** USAGE (comment this line to view) **** | |
var Person = function() { | |
//defaults | |
var _age = 0, | |
_name = 'John Doe'; | |
var socialSecurity = '444 555 666'; | |
var bloodType = 'O negative' | |
//this is a global variable | |
hatSize = 'medium'; | |
var noValue; | |
var aTest = function() { | |
var nestedVar = 'nestedVar'; | |
var nestedFunction = function() { | |
return 'nestedFunction'; | |
}; | |
alert('aTest'); | |
}, | |
anotherTest = function() { | |
alert('anotherTest'); | |
}; | |
function test1() { | |
alert('test1'); | |
var obj = { | |
test3: 'test3', | |
bla: 234 | |
}; | |
function nestedFc() { | |
alert('I am nested!'); | |
} | |
} | |
function test2() { | |
alert('test2'); | |
} | |
function test3() { | |
alert('test3'); | |
return { | |
test3: 'test3', | |
bla: 234 | |
}; | |
} | |
this.initialize = function(name, age) { | |
_name = _name || name; | |
_age = _age || age; | |
}; | |
if (arguments.length) this.initialize(); | |
//public properties. no accessors required | |
this.phoneNumber = '555-224-5555'; | |
this.address = '22 Acacia ave. London, England'; | |
//getters and setters | |
this.getName = function() { return _name; }; | |
this.setName = function (name) { _name = name; }; | |
//private functions | |
function aFunction( arg1 ) { | |
alert('I am a private function (ha!)'); | |
} | |
//public methods | |
this.addBirthday = function() { _age++; }; | |
this.toString = function() { return 'My name is "+_name+" and I am "_age+" years old.'; }; | |
}; | |
//create an instance of a person | |
var rob = Reflection.createExposedInstance(Person); //new Person('Rob', 29); //still 29! (I wish!) | |
//document.write | |
rob._privMems['aFunction'](); //alerts "I am a private function (ha!)" | |
/* */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Suggestions are welcome.