Stubbing Out Inner Functions in The Revealing Module Pattern

The Problem

In this post I would like to discuss an additional problem that you might encounter when unit testing the functions of a module in the revealing module pattern.
We now know how to expose the private members and unit test them. However, we will soon stumble across an issue that is inherent to how the scoping and closure mechanisms work in JavaScript.

define(function(){  
    // inner methods
    var
       draw = function(from, to) { },
       redraw = function(from, to) { },
       addChartContainer = function(argument) {  },
       zoomHandler = function(startDay, daysToShow) {
           ...
           redraw.call();
       },
      .
      .
      .;

    // ctor
    var GanttChart = function(){

    };

    GanttChart.prototype = (function(){
        var publicApi = {
            constructor: GanttChart,
            draw: draw
        };

        return publicApi;
    }());
});

We can see that the zoomHandler function invokes the redraw method which is part of the top level closure. The problem arises when we want to create stubs when testing; in proper unit tests, we would stub out the redraw function when testing the zoomHandler function. The issue is that we cannot directly access the redraw method. Even if we add it to the prototype of the constructor, as we showed in the previous post, we still won’t be able to stub it out. The reason for that is that the redraw function that is invvoked inside zoomHandler uses the local reference that is defined in the closure and not the reference that we expose via the constructor’s prototype. A naive solution would be to consider the logic applied in the redraw function and let the zoomHandler invoke it. But this is against the principles of unit testing; and as such we should avoid it.

My Solution

The way I managed to approach this problem is pretty simple and straightforward. We definitely should have access to the references defined in the module’s closure, but we cannot do that straight from outside the module itself. We can expose other references that point to the same function but not the local references. I used the principles of the conditional revealing module pattern to accomplish my task.

define(['pubsub'], function(pubsub){    
   // inner methods
   var
       draw = function(from, to) { },
       redraw = function(from, to) { },
       addChartContainer = function(argument) {  },
       zoomHandler = function(startDay, daysToShow) {
           ...
           redraw.call();
       },
       .
       .
       .;

    // ctor
    var GanttChart = function(){

    };

    GanttChart.prototype = (function(){
        var publicApi = {
            constructor: GanttChart,
            draw: draw
        };

        pubsub.on({
            topic: 'testMode:on',
            callback: function(){
                $.extend(GanttChart.prototype, {
                    redraw: redraw,
                    addChartContainer: addChartContainer,
                    zoomHandler: zoomHandler,
                    innerEval: function(cmd) { return eval(cmd); }
                });
            }
        });

        return publicApi;
    }());

    return GanttChart;
});

The above snippet is similar to the one I showed in the previous post except for the innerEval method that we add to the constructor’s prototype. This method, in essence, will open the door to the outer world to have access to the local references (or closure members if you will). Don’t be intimidated by the presence of the eval function. Remember that we are using the conditional revealing module pattern, so this will be accessible only in test mode. Truth be told, eval is not all that bad after all. Opening the door to the inner world of a module is not enough; we need a way to utilize that feature. For this reason, I created the following module called closureInspector.

define(function() {
    var
        /**
         * Gets the given closure member of the given module
         *
         * @param {object} module - An instance of the module whose closure is being accessed
         * @param {object} member - The member of the modules closure we're accessing
         */
        getClosureMember = function (module, member) {
            return module.innerEval(member);
        },

        /**
         * Sets the closure member of the module
         *
         * @param {object} module - An instance of the module whose closure is being accessed
         * @param {object} member - The member of the modules closure we're accessing
         * @param {object} value- The value to assign to the given member
         */
        setClosureMember = function (module, member, value) {
            // pass the value as a parameter to the innerEval so that it can be added to 
            // the variable object (scope) of that function. 
            module.innerEval(member + ' = arguments[1]', value);
        },

        /**
         * Replaces the given member of the given module's closure
         *
         * @param {object} module - An instance of the module whose closure is being accessed
         * @param {object} member - The member of the modules closure we're accessing
         * @param {object} replacement- The value to replace the given member
         */
        replaceClosureMember = function(module, member, replacement) {
            var orig = getClosureMember(module, member);
            setClosureMember(module, member, replacement);

            return orig;
        };

    return {
        setClosureMember: setClosureMember,
        getClosureMember: getClosureMember,
        replaceClosureMember: replaceClosureMember
    };
});

So stubbing out the closure members is now an easy task.

define(['ganttChart', 'pubsub'], function (GanttChart, pubsub) {
  describe('The chart module', function () {
     // inform the modules that we are in the test mode
     pubsub.trigger({
         topic: 'testMode:on'
     });

     var ganttChart;

     beforeEach(function () {
        ganttChart = new GanttChart();
     });

     afterEach(function () {
         ganttChart = null;
     });

     describe('The zoomHandler method', function () {
            it('Should call the redraw method with the specified parameters', function() {
                // Arrange
                var redrawSpy = jasmine.createSpy();
                // stub out the redraw function
                closureInspector.replaceClosureMember(ganttChart, 'redraw', redrawSpy);
                ganttChart.startDate = moment();
                ganttChart.daysToShow = 3;

                // Act
                ganttChart.zoomHandler();

                // Assert
                expect(redrawSpy).toHaveBeenCalled();
   }); 

});

Conclusion

Isolating the bits of code that you want to test is crucial for proper unit testing. With the revealing module pattern we can encounter some difficulties in that respect, since we are not able to access the local references of the module. In this post we showed a pretty handy way to enter into the inner of the module using the conditional revealing module pattern and a custom closure inspector.

Comments 0

Leave a Reply