Friday, November 8, 2013

mocha stubs!

I had a dojo widget which procured log data from a dependency handed in at the AMD module's signature and then dumped gunk into a textarea from which a user could browse or copy/paste.

loggingLogger.readLogs(options).then(lang.hitch(this, function(logs) {
   var text = logs.map(function(log) {
      
//more mechanics here
      this.logViewerTextArea.innerHTML = whatever;

 
 

How may we test to ensure that while the textarea starts empty it ultimately gets content? Well, one pain point here turned out to materialize in the fact that the act happens within a promise. This did not cause heartache in mocha testing when tests were ran from an HTML test runner, but in Grunt...

  1. the promise caused false positives in testing until it was forcing back in test with mocha's done()
  2. the tests then crashed the build process as there was no Web SQL database to look into for logs

 
 

Alright, the first step is to "abstract way" loggingLogger so that it may be stubbed.

logger: function() {
   return loggingLogger;
}

 
 

In another refactoring we use the abstraction, replacing loggingLogger with logger. Note that this change and the one above both have to happen in the widget itself.

this.logger().readLogs(options).then(lang.hitch(this, function(logs) {
   var text = logs.map(function(log) {

 
 

In C# one stubs by writing methods which consume interfaces at their signatures and, then in testing, handing in dummy objects which inherit from the interfaces (in lieu of external dependencies which inherit from the interfaces), but in JavaScript one just replaces methods to external dependencies at a greater JavaScript object that one needs to test. An object to stub gets handed into a "factory" which then hands the object back out. During the trip through, the object is given some love. This is like a trip through an assembly line in a factory sure enough, wherein the subject will be spruced up. Think of the visitor pattern in C#.

//factory for stubbing LogViewer methods
define(["dojo/Deferred"], function(Deferred) {
   "use strict";
   function testLogViewerSetup(LogViewer) {
      
      
//stubbing out promise for populating log data
      LogViewer.logger = function() {
         var dummyReadLogs = {
            readLogs : function(options) {
               var deferred = new Deferred();
               
               
//faking log data
               LogViewer.logViewerTextArea.innerHTML = "data";
               setTimeout(function(){
                  deferred.resolve([]);
               }, 1);
               return deferred.promise;
            }
         }
         return dummyReadLogs;
      }
      
      
//end of factory
      return LogViewer;
   }
   return {
      testLogViewerSetup: testLogViewerSetup
   };
});

 
 

Note that I am putting "data" in the logViewerTextArea where I may do so without delving into the asynchronous realm. Also note the empty array in .resolve([]) which allows me to not care about having to fake log.map which would otherwise be a pain point if the promise handed anything else back. My test:

describe("when whatever", function() {
   var widget;
   var textarea;
   
   before(function() {
      widget = new LogViewer();
      widget = LogViewerFactory.testLogViewerSetup(widget);
      widget.callTheThingWhichCallsTheLogger();
      textarea = widget.logViewerTextArea;
   });
   
   it("should populate data", function() {
      assert.isTrue(textarea.innerHTML.length > 0);
   });
});

No comments:

Post a Comment