Skip to content

Commit

Permalink
Merge branch '2027-apply-feedback'
Browse files Browse the repository at this point in the history
  • Loading branch information
fatso83 committed Sep 23, 2019
2 parents 3dd59a5 + 9c4bbf1 commit c3e9512
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 129 deletions.
34 changes: 34 additions & 0 deletions docs/release-source/release/spies.md
Expand Up @@ -32,6 +32,40 @@ handles a callback, as in the following simplified example:
}
```

### Using a spy to wrap all object method

`sinon.spy(object)`

Spies all the object's methods.

Note that it's usually better practice to spy individual methods, particularly on objects that you don't understand or control all the methods for (e.g. library dependencies).

Spying individual methods tests intent more precisely and is less susceptible to unexpected behavior as the object's code evolves.

The following is a slightly contrived example:

```javascript
{
sandbox: sinon.createSandbox(),

setUp: function () {
this.sandbox.spy(jQuery);
},

tearDown: function () {
this.sandbox.restore(); // Unwraps all spied methods
},

"test should inspect jQuery.getJSON's usage of jQuery.ajax": function () {
jQuery.getJSON("/some/resource");

assert(jQuery.ajax.calledOnce);
assertEquals("/some/resource", jQuery.ajax.getCall(0).args[0].url);
assertEquals("json", jQuery.ajax.getCall(0).args[0].dataType);
}
}
```


### Using a spy to wrap an existing method

Expand Down
22 changes: 22 additions & 0 deletions lib/sinon/spy-entire-object.js
@@ -0,0 +1,22 @@
"use strict";

var getPropertyDescriptor = require("./util/core/get-property-descriptor");
var walk = require("./util/core/walk");

function spyEntireObject(spy, object) {
walk(object || {}, function(prop, propOwner) {
// we don't want to spy things like toString(), valueOf(), etc. so we only spy if the object
// is not Object.prototype
if (
propOwner !== Object.prototype &&
prop !== "constructor" &&
typeof getPropertyDescriptor(propOwner, prop).value === "function"
) {
spy(object, prop);
}
});

return object;
}

module.exports = spyEntireObject;
5 changes: 5 additions & 0 deletions lib/sinon/spy.js
Expand Up @@ -9,6 +9,7 @@ var getPropertyDescriptor = require("./util/core/get-property-descriptor");
var deepEqual = require("@sinonjs/samsam").deepEqual;
var isEsModule = require("./util/core/is-es-module");
var spyCall = require("./call");
var spyEntireObject = require("./spy-entire-object");
var wrapMethod = require("./util/core/wrap-method");
var sinonFormat = require("./util/core/format");
var valueToString = require("@sinonjs/commons").valueToString;
Expand Down Expand Up @@ -36,6 +37,10 @@ function spy(object, property, types) {
return spy.create(object);
}

if (!property && typeof object === "object") {
return spyEntireObject(spy, object);
}

if (!object && !property) {
return spy.create(function() {
return;
Expand Down
6 changes: 3 additions & 3 deletions lib/sinon/stub.js
Expand Up @@ -36,11 +36,11 @@ function stub(object, property) {
}

var actualDescriptor = getPropertyDescriptor(object, property);
var isObject = typeof object === "object" || typeof object === "function";
var isStubbingEntireObject = typeof property === "undefined" && isObject;
var isObjectOrFunction = typeof object === "object" || typeof object === "function";
var isStubbingEntireObject = typeof property === "undefined" && isObjectOrFunction;
var isCreatingNewStub = !object && typeof property === "undefined";
var isStubbingNonFuncProperty =
isObject &&
isObjectOrFunction &&
typeof property !== "undefined" &&
(typeof actualDescriptor === "undefined" || typeof actualDescriptor.value !== "function") &&
typeof descriptor === "undefined";
Expand Down
134 changes: 134 additions & 0 deletions test/shared-spy-stub-everything-tests.js
@@ -0,0 +1,134 @@
"use strict";

var referee = require("@sinonjs/referee");
var assert = referee.assert;
var refute = referee.refute;

module.exports = function shared(createSpyOrStub) {
it("replaces all methods of an object when no property is given", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
},
func3: function() {
return;
}
};

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("replaces prototype methods", function() {
function Obj() {
return;
}
Obj.prototype.func1 = function() {
return;
};
var obj = new Obj();

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
});

it("returns object", function() {
var object = {};

assert.same(createSpyOrStub(object), object);
});

it("only replaces functions", function() {
var object = { foo: "bar" };
createSpyOrStub(object);

assert.equals(object.foo, "bar");
});

it("handles non-enumerable properties", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
}
};

Object.defineProperty(obj, "func3", {
value: function() {
return;
},
writable: true,
configurable: true
});

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("handles non-enumerable properties on prototypes", function() {
function Obj() {
return;
}
Object.defineProperty(Obj.prototype, "func1", {
value: function() {
return;
},
writable: true,
configurable: true
});

var obj = new Obj();

createSpyOrStub(obj);

assert.isFunction(obj.func1.restore);
});

it("does not replace non-enumerable properties from Object.prototype", function() {
var obj = {};

createSpyOrStub(obj);

refute.isFunction(obj.toString.restore);
refute.isFunction(obj.toLocaleString.restore);
refute.isFunction(obj.propertyIsEnumerable.restore);
});

it("does not fail on overrides", function() {
var parent = {
func: function() {
return;
}
};
var child = Object.create(parent);
child.func = function() {
return;
};

refute.exception(function() {
createSpyOrStub(child);
});
});

it("throws on non-existent property", function() {
var myObj = {};

assert.exception(function() {
createSpyOrStub(myObj, "ouch");
});

refute.defined(myObj.ouch);
});
};
4 changes: 4 additions & 0 deletions test/spy-test.js
Expand Up @@ -2931,4 +2931,8 @@ describe("spy", function() {
assert.equals(Object.keys(spy), ["aProp"]);
});
});

describe("everything", function() {
require("./shared-spy-stub-everything-tests")(createSpy);
});
});
127 changes: 1 addition & 126 deletions test/stub-test.js
Expand Up @@ -1275,45 +1275,7 @@ describe("stub", function() {
});

describe("everything", function() {
it("stubs all methods of object without property", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
},
func3: function() {
return;
}
};

createStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("stubs prototype methods", function() {
function Obj() {
return;
}
Obj.prototype.func1 = function() {
return;
};
var obj = new Obj();

createStub(obj);

assert.isFunction(obj.func1.restore);
});

it("returns object", function() {
var object = {};

assert.same(createStub(object), object);
});
require("./shared-spy-stub-everything-tests")(createStub);

it("returns function", function() {
var func = function() {
Expand Down Expand Up @@ -1341,83 +1303,6 @@ describe("stub", function() {
assert.isFunction(func.func2.restore);
});

it("only stubs functions", function() {
var object = { foo: "bar" };
createStub(object);

assert.equals(object.foo, "bar");
});

it("handles non-enumerable properties", function() {
var obj = {
func1: function() {
return;
},
func2: function() {
return;
}
};

Object.defineProperty(obj, "func3", {
value: function() {
return;
},
writable: true,
configurable: true
});

createStub(obj);

assert.isFunction(obj.func1.restore);
assert.isFunction(obj.func2.restore);
assert.isFunction(obj.func3.restore);
});

it("handles non-enumerable properties on prototypes", function() {
function Obj() {
return;
}
Object.defineProperty(Obj.prototype, "func1", {
value: function() {
return;
},
writable: true,
configurable: true
});

var obj = new Obj();

createStub(obj);

assert.isFunction(obj.func1.restore);
});

it("does not stub non-enumerable properties from Object.prototype", function() {
var obj = {};

createStub(obj);

refute.isFunction(obj.toString.restore);
refute.isFunction(obj.toLocaleString.restore);
refute.isFunction(obj.propertyIsEnumerable.restore);
});

it("does not fail on overrides", function() {
var parent = {
func: function() {
return;
}
};
var child = Object.create(parent);
child.func = function() {
return;
};

refute.exception(function() {
createStub(child);
});
});

it("does not call getter during restore", function() {
var obj = {
get prop() {
Expand All @@ -1433,16 +1318,6 @@ describe("stub", function() {

stub.restore();
});

it("throws if stubbing non-existent property", function() {
var myObj = {};

assert.exception(function() {
createStub(myObj, "ouch");
});

assert.isUndefined(myObj.ouch);
});
});

describe("stubbed function", function() {
Expand Down

0 comments on commit c3e9512

Please sign in to comment.