sinon
Version:
JavaScript test spies, stubs and mocks.
263 lines (217 loc) • 7.82 kB
JavaScript
;
var commons = require('@sinonjs/commons');
var behavior = require('./behavior.js');
var defaultBehaviors = require('./default-behaviors.js');
var proxy = require('./proxy.js');
var isNonExistentProperty = require('./util/core/is-non-existent-property.js');
var spy = require('./spy.js');
var extend = require('./util/core/extend.js');
var getPropertyDescriptor = require('./util/core/get-property-descriptor.js');
var isEsModule = require('./util/core/is-es-module.js');
var sinonType = require('./util/core/sinon-type.js');
var wrapMethod = require('./util/core/wrap-method.js');
var throwOnFalsyObject = require('./throw-on-falsy-object.js');
var walkObject = require('./util/core/walk-object.js');
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
var commons__default = /*#__PURE__*/_interopDefault(commons);
const { prototypes: commonsPrototypes, functionName, valueToString } = commons__default.default;
const { array: arrayProto, object: objectProto } = commonsPrototypes;
const { hasOwnProperty } = objectProto;
const forEach = arrayProto.forEach;
const pop = arrayProto.pop;
const slice = arrayProto.slice;
const sort = arrayProto.sort;
let uuid = 0;
function createStub(originalFunc) {
// eslint-disable-next-line prefer-const
let proxy$1;
function functionStub() {
const args = slice(arguments);
const matchings = proxy$1.matchingFakes(args);
const fnStub =
pop(
sort(matchings, function (a, b) {
return (
a.matchingArguments.length - b.matchingArguments.length
);
}),
) || proxy$1;
return getCurrentBehavior(fnStub).invoke(this, arguments);
}
proxy$1 = proxy(functionStub, originalFunc || functionStub);
// Inherit spy API:
extend.nonEnum(proxy$1, spy);
// Inherit stub API:
extend.nonEnum(proxy$1, stub);
const name = originalFunc ? functionName(originalFunc) : null;
extend.nonEnum(proxy$1, {
fakes: [],
instantiateFake: createStub,
displayName: name || "stub",
defaultBehavior: null,
behaviors: [],
id: `stub#${uuid++}`,
});
sinonType.set(proxy$1, "stub");
return proxy$1;
}
function stub(object, property) {
if (arguments.length > 2) {
throw new TypeError(
"stub(obj, 'meth', fn) has been removed, see documentation",
);
}
if (isEsModule(object)) {
throw new TypeError("ES Modules cannot be stubbed");
}
throwOnFalsyObject.apply(null, arguments);
if (isNonExistentProperty(object, property)) {
throw new TypeError(
`Cannot stub non-existent property ${valueToString(property)}`,
);
}
const actualDescriptor = getPropertyDescriptor(object, property);
assertValidPropertyDescriptor(actualDescriptor, property);
const isObjectOrFunction =
typeof object === "object" || typeof object === "function";
const isStubbingEntireObject =
typeof property === "undefined" && isObjectOrFunction;
const isCreatingNewStub = !object && typeof property === "undefined";
const isStubbingNonFuncProperty =
isObjectOrFunction &&
typeof property !== "undefined" &&
(typeof actualDescriptor === "undefined" ||
typeof actualDescriptor.value !== "function");
if (isStubbingEntireObject) {
return walkObject(stub, object);
}
if (isCreatingNewStub) {
return createStub();
}
const func =
typeof actualDescriptor.value === "function"
? actualDescriptor.value
: null;
const s = createStub(func);
extend.nonEnum(s, {
rootObj: object,
propName: property,
shadowsPropOnPrototype: !actualDescriptor.isOwn,
restore: function restore() {
if (actualDescriptor !== undefined && actualDescriptor.isOwn) {
Object.defineProperty(object, property, actualDescriptor);
return;
}
delete object[property];
},
});
return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s);
}
function assertValidPropertyDescriptor(descriptor, property) {
if (!descriptor || !property) {
return;
}
if (descriptor.isOwn && !descriptor.configurable && !descriptor.writable) {
throw new TypeError(
`The descriptor for property \`${property}\` is non-configurable and non-writable. ` +
`Sinon cannot stub properties that are immutable. ` +
`See https://sinonjs.org/faq#property-descriptor-errors for help fixing this issue.`,
);
}
if ((descriptor.get || descriptor.set) && !descriptor.configurable) {
throw new TypeError(
`Descriptor for accessor property ${property} is non-configurable`,
);
}
if (isDataDescriptor(descriptor) && !descriptor.writable) {
throw new TypeError(
`Descriptor for data property ${property} is non-writable`,
);
}
}
function isDataDescriptor(descriptor) {
return (
!descriptor.value &&
!descriptor.writable &&
!descriptor.set &&
!descriptor.get
);
}
function getParentBehaviour(stubInstance) {
return stubInstance.parent && getCurrentBehavior(stubInstance.parent);
}
function getDefaultBehavior(stubInstance) {
return (
stubInstance.defaultBehavior ||
getParentBehaviour(stubInstance) ||
behavior.create(stubInstance)
);
}
function getCurrentBehavior(stubInstance) {
const currentBehavior = stubInstance.behaviors[stubInstance.callCount - 1];
return currentBehavior && currentBehavior.isPresent()
? currentBehavior
: getDefaultBehavior(stubInstance);
}
const proto = {
resetBehavior: function () {
this.defaultBehavior = null;
this.behaviors = [];
delete this.returnValue;
delete this.returnArgAt;
delete this.throwArgAt;
delete this.resolveArgAt;
delete this.fakeFn;
this.returnThis = false;
this.resolveThis = false;
forEach(this.fakes, function (fake) {
fake.resetBehavior();
});
},
reset: function () {
this.resetHistory();
this.resetBehavior();
},
onCall: function onCall(index) {
if (!this.behaviors[index]) {
this.behaviors[index] = behavior.create(this);
}
return this.behaviors[index];
},
onFirstCall: function onFirstCall() {
return this.onCall(0);
},
onSecondCall: function onSecondCall() {
return this.onCall(1);
},
onThirdCall: function onThirdCall() {
return this.onCall(2);
},
withArgs: function withArgs() {
const fake = spy.withArgs.apply(this, arguments);
if (this.defaultBehavior && this.defaultBehavior.promiseLibrary) {
fake.defaultBehavior =
fake.defaultBehavior || behavior.create(fake);
fake.defaultBehavior.promiseLibrary =
this.defaultBehavior.promiseLibrary;
}
return fake;
},
};
forEach(Object.keys(behavior), function (method) {
if (
hasOwnProperty(behavior, method) &&
!hasOwnProperty(proto, method) &&
method !== "create" &&
method !== "invoke"
) {
proto[method] = behavior.createBehavior(method);
}
});
forEach(Object.keys(defaultBehaviors), function (method) {
if (hasOwnProperty(defaultBehaviors, method) && !hasOwnProperty(proto, method)) {
behavior.addBehavior(stub, method, defaultBehaviors[method]);
}
});
extend(stub, proto);
module.exports = stub;