Skip to content

Commit

Permalink
stub & spy getters & setters
Browse files Browse the repository at this point in the history
  • Loading branch information
simonzack committed Feb 27, 2015
1 parent fa17f8f commit bb467e6
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 32 deletions.
22 changes: 19 additions & 3 deletions lib/sinon/spy.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,20 @@
"use strict";

(function (sinon) {
function getPropertyDescriptor(object, property) {
var proto = object, descriptor;
while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) {
proto = Object.getPrototypeOf(proto);
}
return descriptor;
}

function makeApi(sinon) {
var push = Array.prototype.push;
var slice = Array.prototype.slice;
var callId = 0;

function spy(object, property) {
function spy(object, property, types) {
if (!property && typeof object == "function") {
return spy.create(object);
}
Expand All @@ -30,8 +38,16 @@
return spy.create(function () { });
}

var method = object[property];
return sinon.wrapMethod(object, property, spy.create(method));
if (types) {
var methodDesc = getPropertyDescriptor(object, property);
for (var i = 0; i < types.length; i++) {
methodDesc[types[i]] = spy.create(methodDesc[types[i]]);
}
return sinon.wrapMethod(object, property, methodDesc);
} else {
var method = object[property];
return sinon.wrapMethod(object, property, spy.create(method));
}
}

function matchingFake(fakes, args, strict) {
Expand Down
16 changes: 13 additions & 3 deletions lib/sinon/stub.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,24 @@
(function (sinon) {
function makeApi(sinon) {
function stub(object, property, func) {
if (!!func && typeof func != "function") {
throw new TypeError("Custom stub should be function");
if (!!func && typeof func != "function" && typeof func != "object") {
throw new TypeError("Custom stub should be a function or a property descriptor");
}

var wrapper;

if (func) {
wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
if (typeof func == "function") {
wrapper = sinon.spy && sinon.spy.create ? sinon.spy.create(func) : func;
} else {
wrapper = func;
if (sinon.spy && sinon.spy.create) {
var types = Object.keys(wrapper);
for (var i = 0; i < types.length; i++) {
wrapper[types[i]] = sinon.spy.create(wrapper[types[i]]);
}
}
}
} else {
var stubLength = 0;
if (typeof object == "object" && typeof object[property] == "function") {
Expand Down
63 changes: 45 additions & 18 deletions lib/sinon/util/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
var div = typeof document != "undefined" && document.createElement("div");
var hasOwn = Object.prototype.hasOwnProperty;

function getPropertyDescriptor(object, property) {
var proto = object, descriptor;
while (proto && !(descriptor = Object.getOwnPropertyDescriptor(proto, property))) {
proto = Object.getPrototypeOf(proto);
}
return descriptor;
}

function isDOMNode(obj) {
var success = false;

Expand Down Expand Up @@ -64,34 +72,55 @@
throw new TypeError("Should wrap property of object");
}

if (typeof method != "function") {
throw new TypeError("Method wrapper should be function");
if (typeof method != "function" && typeof method != "object") {
throw new TypeError("Method wrapper should be a function or a property descriptor");
}
var methodDesc = (typeof method == "function") ? {value: method} : method,
wrappedMethodDesc = getPropertyDescriptor(object, property),
error, i, wrappedMethod;

var wrappedMethod = object[property],
error;

if (!isFunction(wrappedMethod)) {
if (!wrappedMethodDesc) {
error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
property + " as function");
} else if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
} else if (wrappedMethodDesc.restore && wrappedMethodDesc.restore.sinon) {
error = new TypeError("Attempted to wrap " + property + " which is already wrapped");
} else if (wrappedMethod.calledBefore) {
var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
error = new TypeError("Attempted to wrap " + property + " which is already " + verb);
}

if (error) {
if (wrappedMethod && wrappedMethod.stackTrace) {
error.stack += "\n--------------\n" + wrappedMethod.stackTrace;
if (wrappedMethodDesc && wrappedMethodDesc.stackTrace) {
error.stack += "\n--------------\n" + wrappedMethodDesc.stackTrace;
}
throw error;
}

var types = Object.keys(methodDesc);
for (i = 0; i < types.length; i++) {
wrappedMethod = wrappedMethodDesc[types[i]];
if (!isFunction(wrappedMethod)) {
error = new TypeError("Attempted to wrap " + (typeof wrappedMethod) + " property " +
property + " as function");
} else if (wrappedMethod.restore && wrappedMethod.restore.sinon) {
error = new TypeError("Attempted to wrap " + property + " which is already wrapped");
} else if (wrappedMethod.calledBefore) {
var verb = !!wrappedMethod.returns ? "stubbed" : "spied on";
error = new TypeError("Attempted to wrap " + property + " which is already " + verb);
}
if (error) {
if (wrappedMethod && wrappedMethod.stackTrace) {
error.stack += "\n--------------\n" + wrappedMethod.stackTrace;
}
throw error;
}
}

// IE 8 does not support hasOwnProperty on the window object and Firefox has a problem
// when using hasOwn.call on objects from other frames.
var owned = object.hasOwnProperty ? object.hasOwnProperty(property) : hasOwn.call(object, property);
object[property] = method;
mirrorProperties(methodDesc, wrappedMethodDesc);
for (i = 0; i < types.length; i++) {
mirrorProperties(methodDesc[types[i]], wrappedMethodDesc[types[i]]);
}
Object.defineProperty(object, property, methodDesc);

method.displayName = property;
// Set up a stack trace which can be used later to find what line of
// code the original method was created on.
Expand All @@ -103,14 +132,12 @@
// via direct assignment.
if (!owned) {
delete object[property];
}
if (object[property] === method) {
object[property] = wrappedMethod;
} else {
Object.defineProperty(object, property, wrappedMethodDesc);
}
};

method.restore.sinon = true;
mirrorProperties(method, wrappedMethod);

return method;
};
Expand Down
4 changes: 2 additions & 2 deletions test/sinon/stub_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -519,11 +519,11 @@ buster.testCase("sinon.stub", {
assert.isFunction(stub.restore);
},

"throws if third argument is provided but not function": function () {
"throws if third argument is provided but not a function or proprety descriptor": function () {
var object = this.object;

assert.exception(function () {
sinon.stub(object, "method", {});
sinon.stub(object, "method", 1);
}, "TypeError");
},

Expand Down
40 changes: 34 additions & 6 deletions test/sinon_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,14 @@ buster.testCase("sinon", {
".wrapMethod": {
setUp: function () {
this.method = function () {};
this.object = { method: this.method };
this.getter = function () {};
this.setter = function () {};
this.object = {method: this.method};
Object.defineProperty(this.object, "property", {
get: this.getter,
set: this.setter,
configurable: true
});
},

"is function": function () {
Expand Down Expand Up @@ -61,11 +68,11 @@ buster.testCase("sinon", {
}, "TypeError");
},

"throws if third argument is not function": function () {
"throws if third argument is not a function or a property descriptor": function () {
var object = this.object;

assert.exception(function () {
sinon.wrapMethod(object, "method", {});
sinon.wrapMethod(object, "method", 1);
}, "TypeError");
},

Expand All @@ -76,12 +83,33 @@ buster.testCase("sinon", {
assert.isFunction(this.object.method);
},

"replaces getter": function () {
sinon.wrapMethod(this.object, "property", { get: function () {} });

refute.same(this.getter, Object.getOwnPropertyDescriptor(this.object, "property").get);
assert.isFunction(Object.getOwnPropertyDescriptor(this.object, "property").get);
},

"replaces setter": function () {
sinon.wrapMethod(this.object, "property", { set: function () {} });

refute.same(this.setter, Object.getOwnPropertyDescriptor(this.object, "property").set);
assert.isFunction(Object.getOwnPropertyDescriptor(this.object, "property").set);
},

"throws if method is already wrapped": function () {
var object = { method: function () {} };
sinon.wrapMethod(object, "method", function () {});
sinon.wrapMethod(this.object, "method", function () {});

assert.exception(function () {
sinon.wrapMethod(object, "method", function () {});
sinon.wrapMethod(this.object, "method", function () {});
}, "TypeError");
},

"throws if property descriptor is already wrapped": function () {
sinon.wrapMethod(this.object, "property", { get: function () {} });

assert.exception(function () {
sinon.wrapMethod(this.object, "property", { get: function () {} });
}, "TypeError");
},

Expand Down

0 comments on commit bb467e6

Please sign in to comment.