lib/stub.js
// record stats about the call to the stubbed/spied method
record stats about the call to the stubbed/spied method
record stats about the call to the stubbed / spied method
function recordMethod( stub, interceptedMethod, interceptedThisObject, args ) {
    stub.callNumber = ++_callCounter;
    stub.callCount += 1;
    stub.called = true;
    stub.callArguments = args.slice();
    var isSaveInfo = stub._callResults.length < stub._saveLimit;
    if (isSaveInfo) stub._callArguments.push(stub.callArguments);
    if (isSaveInfo) stub._callNumbers.push(stub.callNumber);
    var cb, cbix;
    for (var i=0; i<args.length; i++) if (typeof args[i] === 'function') { cb = args[i]; cbix = i; break; }
    if (interceptedMethod !== 'stub') {
        if (cb) {
            args[cbix] = function _spyCallback() {
                var argv = new Array();
                for (var i=0; i<arguments.length; i++) argv[i] = arguments[i];
                if (isSaveInfo) stub._callCallbackArguments.push(argv);
                stub.callCallbackArguments = argv;
                var ret = _tryApply(cb, null, argv);
                stub.callCallbackError = ret.err;
                if (isSaveInfo) stub._callCallbackErrors.push(ret.err);
                if (ret.isError) throw ret.err;
                return ret.ret;
            }
        }
    }
    if (--stub._restoreAfterCalls <= 0) stub.restore();
    if (interceptedMethod === 'stub') {
        var ret = stub._retvals[0] || new Retval().once(true);
        if (ret._once) stub._retvals.shift();
        if (ret.yields) {
            if (ret._async) setTimeout(function(){ cb.apply(null, ret.yields) }, 2);
            else if (ret.isError) setImmediate(function(){ cb.apply(null, ret.yields) });
            else cb.apply(null, ret.yields);
        }
    } else {
        var ret = _tryApply(interceptedMethod, interceptedThisObject, args);
    }
    stub.callReturn = ret.ret;
    stub.callResult = ret.ret;
    stub.callError = ret.err;
    if (ret.isError) stub.error = ret.err;
    if (isSaveInfo) stub._callErrors.push(ret.err);
    if (isSaveInfo) stub._callResults.push(ret.ret);
    if (ret.isError) throw ret.err;
    else return ret.ret;
}
function recordMethod ( stub , interceptedMethod , interceptedThisObject , args ) { stub . callNumber = ++ _callCounter ; stub . callCount += 1 ; stub . called = true ; stub . callArguments = args . slice ( ) ; var isSaveInfo = stub . _callResults . length < stub . _saveLimit ; if ( isSaveInfo ) stub . _callArguments . push ( stub . callArguments ) ; if ( isSaveInfo ) stub . _callNumbers . push ( stub . callNumber ) ; var cb , cbix ; for ( var i = 0 ; i < args . length ; i ++ ) if ( typeof args [ i ] === 'function' ) { cb = args [ i ] ; cbix = i ; break ; } if ( interceptedMethod !== 'stub' ) { if ( cb ) { args [ cbix ] = function _spyCallback ( ) { var argv = new Array ( ) ; for ( var i = 0 ; i < arguments . length ; i ++ ) argv [ i ] = arguments [ i ] ; if ( isSaveInfo ) stub . _callCallbackArguments . push ( argv ) ; stub . callCallbackArguments = argv ; var ret = _tryApply ( cb , null , argv ) ; stub . callCallbackError = ret . err ; if ( isSaveInfo ) stub . _callCallbackErrors . push ( ret . err ) ; if ( ret . isError ) throw ret . err ; return ret . ret ; } } } if ( -- stub . _restoreAfterCalls <= 0 ) stub . restore ( ) ; if ( interceptedMethod === 'stub' ) { var ret = stub . _retvals [ 0 ] || new Retval ( ) . once ( true ) ; if ( ret . _once ) stub . _retvals . shift ( ) ; if ( ret . yields ) { if ( ret . _async ) setTimeout ( function ( ) { cb . apply ( null , ret . yields ) } , 2 ) ; else if ( ret . isError ) setImmediate ( function ( ) { cb . apply ( null , ret . yields ) } ) ; else cb . apply ( null , ret . yields ) ; } } else { var ret = _tryApply ( interceptedMethod , interceptedThisObject , args ) ; } stub . callReturn = ret . ret ; stub . callResult = ret . ret ; stub . callError = ret . err ; if ( ret . isError ) stub . error = ret . err ; if ( isSaveInfo ) stub . _callErrors . push ( ret . err ) ; if ( isSaveInfo ) stub . _callResults . push ( ret . ret ) ; if ( ret . isError ) throw ret . err ; else return ret . ret ; }
record stats about the call to the stubbed/spied method
record stats about the call to the stubbed / spied method
// assume the first function is the callback // also intercept callbacks the spied-on function might make // record stats about the stubbed/spied function callback too // TODO: conceptually, this is almost identical to stubbing a method, //   should be some way of better factoring out the commonalities // TODO: need an option to turn off callback interception // TODO: tie callback to the call by call-id (or store into per-call struct) // TODO: tag errors with the call id // restore before the callback so caller sees the unmodified object // mock the method returns/yields/throws behavior // note: nodejs v8 does not guarantee that the clock will have advanced by 2 ms // invoke the specified function/method/overrideMethod // return value is only available after the call returns // TODO: distinguish thrown null / undefined from "no error"