Testing Private JavaScript : “To Test, Or Not To Test”

Testing private JavaScript, that is JavaScript in closures, has always been a dilemma for the JavaScript Developer that wants to practice TDD. There have been many debates and schools of thought around this. Some believe that private JavaScript functions and objects should not be tested and attempting to do so could violate good JavaScript design practice. Such reasoning however ignores the virtues of TDD in contributing to code and overall application quality.

Good JavaScript design practice dictates that you will probably want to abstract all your core functionality into a closure, essentially making your core objects and functions private. The main reason for this is that client-side JavaScript is easily accessible within the user’s browser. Thus all your functionality and implementation code is available and viewable within the browser. Also if there are other JavaScript functions publicly accessible within the page that has a similar definition to the functions in your library, it could cause conflicts when trying to interact with them at runtime. Furthermore following object-oriented rules of encapsulation and abstraction, you want to extract your internal implementation into private objects and functions away from the public view.

However this presents a problem, as functionality within closures are not accessible publicly and thus are inaccessible by testing frameworks to be able to unit test your code. So if you are an avid proponent of TDD and want to ensure your code is robust and working as it should, while adhering to good JavaScript design principles, how do you get around this and strike a balance?

Testing Private functions through a Public Interface

The first thing you probably want to do is create a public interface for your library to expose your functionality publicly. An example of how this would look is given here:


(function(){ //opens the closure
//definition of private object Animal
var Animal= function(){
this.name="";
};
Animal.prototype={
getAnimal : function(code){
if(code=="c"){
this.name="Cat";
}
else if(code=="d"){
this.name="Dog";
}
else{
this.name="unknown";
}
}
};
//myLib public interface
window.myLib = {
selectAnimal : function(code){
var selector=new Animal();
selector.getAnimal(code);
return selector.name;
}
};
})(); //closes the closure
//Call selectAnimal publicly
window.myLib.selectAnimal();

view raw

example1.js

hosted with ❤ by GitHub

In the example above, we have a closure that contains a public interface attached to window called myLib and a private object called Animal that does all the heavy lifting. MyLib is accessible publicly and has a public function selectAnimal that takes a parameter called code. All selectAnimal does is create an instance of Animal and returns the value of the Animal object’s name property which is set from a call to it’s getAnimal function with the specified code.

It is the getAnimal function that does all the work. It takes the code specified and through a series of conditions, sets the name property of Animal with the specific name of the animal.

Whereas myLib.selectAnimal is exposed publicly and can be tested easily. Animal is defined within the closure and is private and all it’s related properties and functions are also private, and thus inaccessible by any testing framework.

To ensure getAnimal is fully tested we have to use the public function to fully exercise every scenario that the private function would get called with.

An example of tests for this using the Jasmine Testing framework would be:


describe("The selectAnimal function", function(){
it("should return Cat"){
expect(window.myLib.selectAnimal("c")).toBe("Cat");
};
it("should return Dog"){
expect(window.myLib.selectAnimal("d")).toBe("Dog");
};
it("should return unknown"){
expect(window.myLib.selectAnimal()).toBe("unknown");
}
});

These tests would fully test both the public function selectAnimal and the private function getAnimal by mimicking every scenario for selectAnimal in which getAnimal may be called.

Hence when conceptualizing our tests, in true TDD style, conceptualize your tests for the private functions and view the public function as just a wrapper for your core implementation.

Though it might be tricky, depending on the level of complexity, to conceptualize tests for every scenario that would exercise both a public function and any potential private functions it may use, it is not impossible and if you are following detailed methods of conceptualizing your tests, this shouldn’t be difficult.

The same approach goes for standalone functions defined within the closure. For example, lets modify our code to let selectAnimal receive a JSON object, and we send that to a utility function defined within the closure:


(function(){ //opens the closure
//utility function
function extractCode(obj){
return obj.code;
};
//definition of private object Animal
var Animal= function(){
this.name="";
};
Animal.prototype={
getAnimal : function(code){
if(code=="c"){
this.name="Cat";
}
else if(code=="d"){
this.name="Dog";
}
else{
this.name="unknown";
}
}
};
//myLib public interface
window.myLib = {
selectAnimal : function(params){
var selector=new Animal();
var code = extractCode(params);
selector.getAnimal(code);
return selector.name;
}
};
})(); //closes the closure
//Call selectAnimal publicly with JSON data
window.myLib.selectAnimal({"code":"d"});

view raw

example2.js

hosted with ❤ by GitHub

Here we added a utility function extractCode that is a standalone function defined within the closure, and is thus also private. We use extractCode to extract the value for code from the JSON object received by selectAnimal. This example would not require any additional tests for selectAnimal to exercise extractCode as well since all extractCode does is return the code, but you get the general idea.

An Alternative Approach: Adding a Public Instance of the Private Object

Another approach would be to add a public instance of the private object to the public interface. So we could modify our solution to include a property of myLib that instantiates an instance of Animal. For example:


(function(){ //opens the closure
//definition of private object Animal
var Animal= function(){
this.name="";
};
Animal.prototype={
getAnimal : function(code){
if(code=="c"){
this.name="Cat";
}
else if(code=="d"){
this.name="Dog";
}
else{
this.name="unknown";
}
}
};
//myLib public interface
window.myLib = {
selector : null,
initialize : function(){
//public property instantiates an instance of Animal
this.selector= new Animal();
},
selectAnimal : function(code){
this.selector.getAnimal(code);
return this.selector.name;
}
};
})(); //closes the closure
//initializes selector publicly
window.myLib.initialize();
//Call selectAnimal publicly
//Returns 'Dog'
window.myLib.selectAnimal("d");

view raw

example3.js

hosted with ❤ by GitHub

So here, all we do is make the selector variable a public property of window.myLib and use it to initialize an instance of the Animal object in our initialize function. So now when we call the getAnimal function, we use the this self referencer to access the public property like so: this.selector.getAnimal.

This now gives us an instance of Animal that is accessible publicly. We can use that in our tests to test the private object and its related functions directly. Example:


describe("The getAnimal function", function(){
beforeEach(function(){
window.myLib.initialize();
});
it("should set Animal name property to Cat"){
window.myLib.selector.getAnimal("c")
expect(window.myLib.selector.name).toBe("Cat");
};
it("should set Animal name property to Dog"){
window.myLib.selector.getAnimal("d")
expect(window.myLib.selector.name).toBe("Dog");
}
it("should set Animal name property to unknown"){
window.myLib.selector.getAnimal()
expect(window.myLib.selector.name).toBe("unknown");
}
});
describe("The selectAnimal function", function(){
it("should call the getAnimal function"){
window.myLib.initialize();
spyOn(window.myLib.selector, "getAnimal");
window.myLib.selectAnimal();
expect(window.myLib.selector.getAnimal).toHaveBeenCalled();
};
});

Now that we are able to test getAnimal independently, we can reduce our tests for selectAnimal, as shown.

Personally I prefer this approach as I consider it perhaps the easiest and cleanest way to expose your private functionality to your tests without severely violating good JavaScript design principles.

Testing Callback functions without mocking asynchronous calls

Callback functions are commonly used in asynchronous calls such as AJAX requests. Though there are many third party mocking libraries that simulate asynchronous calls that can allow these functions to be tested, in an environment with little or no use of third party libraries, this can be a real challenge to test. Well now that we have our public instance of our private object, this becomes significantly simpler. Lets modify our code snippet to include an AJAX call using JQuery’s ajax function:


(function(){ //opens the closure
//definition of private object Animal
var Animal= function(){
this.name="";
};
Animal.prototype={
getAnimal : function(code){
//AJAX call to get the description
$.ajax({
url : "getDescription.json",
dataType : "json",
data : JSON.stringify({"code" : code}),
success : function(response){
this.name=response;
}
});
}
};
//myLib public interface
window.myLib = {
selector : null,
initialize : function(){
//public property instantiates an instance of Animal
this.selector= new Animal();
},
selectAnimal : function(code){
this.selector.getAnimal(code);
return this.selector.name;
}
};
})(); //closes the closure
//initializes selector publicly
window.myLib.initialize();
//Call selectAnimal publicly
//Returns 'Dog'
window.myLib.selectAnimal("d");

view raw

example4.js

hosted with ❤ by GitHub

In the example above we modified our getAnimal function to include an AJAX call made to an endpoint getDescription.json that returns the appropriate description based on the code supplied. Upon a success response, a function is called that sets the name property of the Animal object.

Without the use of some third party library for mocking asynchronous calls, it would be near impossible to test that success function. However making a slight modification to the code can make this function more testable. Example:


(function(){ //opens the closure
//definition of private object Animal
var Animal= function(){
this.name="";
};
Animal.prototype={
getAnimal : function(code){
var s = this;
s.getAnimalCallBackFn = function(response){
this.name=response;
};
//AJAX call to get the description
$.ajax({
url : "getDescription.json",
dataType : "json",
data : JSON.stringify({"code" : code}),
success : s.getAnimalCallBackFn
});
}
};
//myLib public interface
window.myLib = {
selector : null,
initialize : function(){
//public property instantiates an instance of Animal
this.selector= new Animal();
},
selectAnimal : function(code){
this.selector.getAnimal(code)
return this.selector.name;
}
};
})(); //closes the closure
//initializes selector publicly
window.myLib.initialize();
//Call selectAnimal publicly
//Returns 'Dog'
window.myLib.selectAnimal("d");

view raw

example5.js

hosted with ❤ by GitHub

So all we do here is make the callback function a method of the Animal object called getAnimalCallBackFn. So now whenever we create an instance of Animal, we will have access to the getAnimalCallBackFn independently of the ajax call.

Now we can use our public instance of Animal, selector, to test getAnimalCallBackFn. Example:


describe("The getAnimalCallBackFn function", function(){
it("should set Animal name property"){
window.myLib.initialize();
window.myLib.selector.getAnimal ();
window.myLib.selector.getAnimalCallBackFn("dog");
expect(window.myLib.selector.name).toBe("dog");
};
});

Conclusion

Testing private JavaScript functions and objects can be a challenge, but there is no reason for the avid JavaScript TDD enthusiast to feel deterred by those who believe that these functions should not be unit tested. Furthermore testing callback functions can be done independently of asynchronous calls if we have limited ability to use third party libraries. Indeed, we can write testable JavaScript that also adheres to good JavaScript design practice. There are many ways to make your JavaScript more testable, and these are just a few pointers I have found helpful. So then, “Go Forth and Test!”.

One thought on “Testing Private JavaScript : “To Test, Or Not To Test”

Leave a comment