blob: 8af0e1ec164424b14d6968cfefbedfca1a2f81d5 [file] [log] [blame]
var path = require('path');
function Gently() {
this.expectations = [];
this.hijacked = {};
var self = this;
process.addListener('exit', function() {
self.verify('process exit');
});
};
module.exports = Gently;
Gently.prototype.stub = function(location, exportsName) {
function Stub() {
return Stub['new'].apply(this, arguments);
};
Stub['new'] = function () {};
var stubName = 'require('+JSON.stringify(location)+')';
if (exportsName) {
stubName += '.'+exportsName;
}
Stub.prototype.toString = Stub.toString = function() {
return stubName;
};
var exports = this.hijacked[location] || {};
if (exportsName) {
exports[exportsName] = Stub;
} else {
exports = Stub;
}
this.hijacked[location] = exports;
return Stub;
};
Gently.prototype.hijack = function(realRequire) {
var self = this;
return function(location) {
return self.hijacked[location] = (self.hijacked[location])
? self.hijacked[location]
: realRequire(location);
};
};
Gently.prototype.expect = function(obj, method, count, stubFn) {
if (typeof obj != 'function' && typeof obj != 'object' && typeof obj != 'number') {
throw new Error
( 'Bad 1st argument for gently.expect(), '
+ 'object, function, or number expected, got: '+(typeof obj)
);
} else if (typeof obj == 'function' && (typeof method != 'string')) {
// expect(stubFn) interface
stubFn = obj;
obj = null;
method = null;
count = 1;
} else if (typeof method == 'function') {
// expect(count, stubFn) interface
count = obj;
stubFn = method;
obj = null;
method = null;
} else if (typeof count == 'function') {
// expect(obj, method, stubFn) interface
stubFn = count;
count = 1;
} else if (count === undefined) {
// expect(obj, method) interface
count = 1;
}
var name = this._name(obj, method, stubFn);
this.expectations.push({obj: obj, method: method, stubFn: stubFn, name: name, count: count});
var self = this;
function delegate() {
return self._stubFn(this, obj, method, name, Array.prototype.slice.call(arguments));
}
if (!obj) {
return delegate;
}
var original = (obj[method])
? obj[method]._original || obj[method]
: undefined;
obj[method] = delegate;
return obj[method]._original = original;
};
Gently.prototype.restore = function(obj, method) {
if (!obj[method] || !obj[method]._original) {
throw new Error(this._name(obj, method)+' is not gently stubbed');
}
obj[method] = obj[method]._original;
};
Gently.prototype.verify = function(msg) {
if (!this.expectations.length) {
return;
}
var validExpectations = [];
for (var i = 0, l = this.expectations.length; i < l; i++) {
var expectation = this.expectations[i];
if (expectation.count > 0) {
validExpectations.push(expectation);
}
}
this.expectations = []; // reset so that no duplicate verification attempts are made
if (!validExpectations.length) {
return;
}
var expectation = validExpectations[0];
throw new Error
( 'Expected call to '+expectation.name+' did not happen'
+ ( (msg)
? ' ('+msg+')'
: ''
)
);
};
Gently.prototype._stubFn = function(self, obj, method, name, args) {
var expectation = this.expectations[0], obj, method;
if (!expectation) {
throw new Error('Unexpected call to '+name+', no call was expected');
}
if (expectation.obj !== obj || expectation.method !== method) {
throw new Error('Unexpected call to '+name+', expected call to '+ expectation.name);
}
expectation.count -= 1;
if (expectation.count === 0) {
this.expectations.shift();
// autorestore original if its not a closure
// and no more expectations on that object
var has_more_expectations = this.expectations.reduce(function (memo, expectation) {
return memo || (expectation.obj === obj && expectation.method === method);
}, false);
if (obj !== null && method !== null && !has_more_expectations) {
if (typeof obj[method]._original !== 'undefined') {
obj[method] = obj[method]._original;
delete obj[method]._original;
} else {
delete obj[method];
}
}
}
if (expectation.stubFn) {
return expectation.stubFn.apply(self, args);
}
};
Gently.prototype._name = function(obj, method, stubFn) {
if (obj) {
var objectName = obj.toString();
if (objectName == '[object Object]' && obj.constructor.name) {
objectName = '['+obj.constructor.name+']';
}
return (objectName)+'.'+method+'()';
}
if (stubFn.name) {
return stubFn.name+'()';
}
return '>> '+stubFn.toString()+' <<';
};