Search code examples
javascriptnode.jsdecoratorsinonstub

Sinon stub an object containing sync and async functions


I am working on a project where I am observing types of each binding layer function that node.js javascript layer calls. For observing types, I created a stub using sinon that looks something like this

var originalProcessBinding = process.binding;
sinon.stub(process, 'binding').callsFake(function (data) {
  var res = originalProcessBinding(data);
  // custom code here
  return res;
}

So, my idea is to look at each object inside res and see if its a Function. If it is, create a stub that records the state and then call the original Function. The custom code looks something like

_.forEach(res, function(value, key) {
  if (_.isFunction(value)) {
    sinon.stub(res, key).callsFake(function() {
      var args = arguments;
      // do some processing with the arguments
      save(args);
      // call the original function
      return value(...arguments);
    }
  }
}

However, I am not sure if this handles all the types of returns. For instance, how are the errors handled? What happens if the function is asynchronous?

I ran the node.js test suite and found lots of failing test cases. Is there a better way to stub the functions. Thanks.

Edit: The failing test cases have errors in common that look like Callback was already called or Timeout or Expected Error.


Solution

  • Unfortunately, even though many errors can be fixed, It's hard to add sinon to the build process. I implemented by own stubbing methods in vanilla js to fix this. Anyone who's looking to stub internal node.js functions should find this helpful.

    (function() { process.binding = function(args) {
        const org = process.binding;
        const util = require('util');
    
        var that = org(args),
          thatc = that;
          for (let i in thatc) {
            if (util.isFunction(thatc[i]) && (!thatc[i].__isStubbed)) {
              let fn = thatc[i];
              if (i[0] !== i[0].toUpperCase()) {
                // hacky workaround to avoid stubbing function constructors.
                thatc[i] = function() {
                  save(arguments);
                  return fn.apply(that, arguments);
                }
                thatc[i].__isStubbed = true; 
              }
            }
          }
        return thatc;
      } 
    })();
    

    This code passes all the tests with the current master of Node.js. Adding sinon seems to mutate the function object that triggers internal v8 checks which fail.