Streamline with Sinon stubs

21 views
Skip to first unread message

Anssi Törmä

unread,
Dec 10, 2016, 12:08:04 PM12/10/16
to streamline.js
I've been struggling for some time with Sinon and Streamline. I am stubbing a method of a collaborator object that has a streamlined method: 

sinon.stub(collaborator, 'someStreamlinedMethod').yields(null, successReturnValue).

The problem is that the original method (Streamline proxy) is called anyway. With non-streamlined method the Sinon proxy kicks in as expected. 

I finally discovered a workaround. After stubbing, delete the properties named like fiberized from the Sinon proxies. When using other generation options than fibers, the key is probably named differently (haven't checked what they are or if this workaround even applies).

A helper function using lodash:

/**
* "Enables" Sinon proxies (stubs, mocks) of Streamline.js proxy functions. Call this function
* after you have stubbed methods of obj. Otherwise original Streamline.js proxies are called
* regardless of Sinon proxying.
*
* Works by removing the 'fiberized' properties from the Sinon proxy functions. Sinon.restore()
* restores the original Streamline.js proxy with its fiberized properties.
*
* @param {Object} obj - object whose Sinon proxies to "enable"
*/
function enableSinonStubs(obj) {
  _.forOwn(obj, function (value) {
if (value.isSinonProxy) {
_.forOwn(value, function (nestedValue, nestedKey) {
        // Assuming that we're using the fibers generation option. 
        // The key is different with other options.
if (_.startsWith(nestedKey, 'fiberized')) {
delete value[nestedKey];
}
})
}
});
}

Hope this helps someone to avoid the frustation and bafflement I experienced.

Bruno Jouhier

unread,
Dec 10, 2016, 7:47:49 PM12/10/16
to streamline.js
Hi Anssi,

This makes a lot of sense (and sorry for the trouble!).

Streamline transforms every function with an _ parameter into 2 functions: a visible function and a hidden one. The visible function is the function in which _ behaves like a node callback. The hidden function depends on the runtime (fibers, generators, callbacks) and is used to optimize calls between streamlined functions: when a streamlined function calls another streamlined function, it short-circuits the visible function and calls the hidden function directly. 

The link between the visible function and the hidden one is handled by a 'fiberized-N' property in fibers mode and by a 'starred-N' property in other modes (where N is the index of the _ in the args list).

In fibers mode, a call via the hidden function is MUCH FASTER than a call via the visible function, but the visible function is necessary for interop with regular node.js code. This is why two functions are generated.

Hope this helps.

Bruno

Anssi Törmä

unread,
Dec 11, 2016, 3:02:55 PM12/11/16
to streamline.js
Hi Bruno,

Makes sense. Do you see that removing the hidden function while stubbing has any undesireable side effects when testing (apart from performance)? Or is there a better way to get mock/stub proxies to work with Streamline? 

- Anssi

Bruno Jouhier

unread,
Dec 11, 2016, 3:23:03 PM12/11/16
to stream...@googlegroups.com
As a general rule, the only side effect will be on performance and I don't see a better way.

But there are cases where stubbing won't work and the code will actually break if you remove the 'fiberized-N' property, because the transpiler optimized and generated a direct call to the hidden function. This happens when the transpiler can resolve the function call 'statically'. For example in a situation like

function foo(_) {
  function bar(_) { ... }
  return bar(_);
}

This is transpiled to:

var foo = _streamline.async(function _$$foo$$(_2) {
  var bar = _streamline.async(function _$$bar$$(_3) {
    return 1;
  }, 0, 1);
  return bar["fiberized-0"](true);
}, 0, 1);

If you delete the "fiberized-0" property on bar, this code will break.

But this won't happen if you stub methods because method calls are resolved dynamically. So you should be on the safe side.

Bruno

Anssi Törmä

unread,
Dec 13, 2016, 8:35:22 AM12/13/16
to streamline.js
All right, thanks for the answers, Bruno!

The kind of situation you described probably doesn't happen in practice when unit testing. I could not mock the internal function bar() when testing foo() since I don't have access to it. If I needed to, I could move it to a separate component. The functions I need to mock are usually in objects loaded by require().

-Anssi

Reply all
Reply to author
Forward
0 new messages