Skip to content

Instantly share code, notes, and snippets.

@ssaunders
Created September 4, 2014 17:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save ssaunders/64d8c20e5506e3ccd74b to your computer and use it in GitHub Desktop.
Save ssaunders/64d8c20e5506e3ccd74b to your computer and use it in GitHub Desktop.
Expose private variables for unit testing in JavaScript
/* 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!)"
/* */
@ssaunders
Copy link
Author

Suggestions are welcome.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment