From 278c2ce66cb88e81e3752c672ed630369f15fc34 Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Sat, 25 Feb 2017 23:25:53 -0300 Subject: [PATCH 1/5] Add getters/setters stub behaviors --- lib/sinon/default-behaviors.js | 20 +++++++ lib/sinon/stub.js | 21 ++++++- test/stub-test.js | 102 ++++++++++++++++++++++++++------- 3 files changed, 120 insertions(+), 23 deletions(-) diff --git a/lib/sinon/default-behaviors.js b/lib/sinon/default-behaviors.js index 263c500ca..469dd75d3 100644 --- a/lib/sinon/default-behaviors.js +++ b/lib/sinon/default-behaviors.js @@ -163,6 +163,26 @@ module.exports = { callThrough: function callThrough(fake) { fake.callsThrough = true; + }, + + get: function get(fake, getterFunction) { + var rootStub = fake.stub || fake; + + Object.defineProperty(rootStub.rootObj, rootStub.propName, { + get: getterFunction + }); + + return fake; + }, + + set: function set(fake, setterFunction) { + var rootStub = fake.stub || fake; + + Object.defineProperty(rootStub.rootObj, rootStub.propName, { // eslint-disable-line accessor-pairs + set: setterFunction + }); + + return fake; } }; diff --git a/lib/sinon/stub.js b/lib/sinon/stub.js index 7580dda9f..b0c346e0e 100644 --- a/lib/sinon/stub.js +++ b/lib/sinon/stub.js @@ -5,6 +5,7 @@ var behaviors = require("./default-behaviors"); var spy = require("./spy"); var extend = require("./util/core/extend"); var functionToString = require("./util/core/function-to-string"); +var getPropertyDescriptor = require("./util/core/get-property-descriptor"); var wrapMethod = require("./util/core/wrap-method"); var stubEntireObject = require("./stub-entire-object"); var stubDescriptor = require("./stub-descriptor"); @@ -13,24 +14,38 @@ var throwOnFalsyObject = require("./throw-on-falsy-object"); function stub(object, property, descriptor) { throwOnFalsyObject.apply(null, arguments); + var actualDescriptor = getPropertyDescriptor(object, property); var isStubbingEntireObject = typeof property === "undefined" && typeof object === "object"; var isCreatingNewStub = !object && typeof property === "undefined"; var isStubbingDescriptor = object && property && Boolean(descriptor); + var isStubbingNonFuncProperty = typeof object === "object" + && typeof property !== "undefined" + && (typeof actualDescriptor === "undefined" + || typeof actualDescriptor.value !== "function") + && typeof descriptor === "undefined"; var isStubbingExistingMethod = !isStubbingDescriptor && typeof object === "object" - && typeof object[property] === "function"; + && typeof actualDescriptor !== "undefined" + && typeof actualDescriptor.value === "function"; var arity = isStubbingExistingMethod ? object[property].length : 0; if (isStubbingEntireObject) { return stubEntireObject(stub, object); } + if (isStubbingDescriptor) { + return stubDescriptor.apply(null, arguments); + } + if (isCreatingNewStub) { return stub.create(); } - if (isStubbingDescriptor) { - return stubDescriptor.apply(null, arguments); + if (isStubbingNonFuncProperty) { + var s = stub.create(); + s.rootObj = object; + s.propName = property; + return s; } return wrapMethod(object, property, stub.create(arity)); diff --git a/test/stub-test.js b/test/stub-test.js index 8660c9f5a..faefa2705 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -800,16 +800,6 @@ describe("stub", function () { assert.isFalse(stub.called); }); - it("throws if property is not a function", function () { - var obj = { someProp: 42 }; - - assert.exception(function () { - createStub(obj, "someProp"); - }); - - assert.equals(obj.someProp, 42); - }); - it("successfully stubs falsey properties", function () { var obj = { 0: function () { } }; @@ -943,16 +933,6 @@ describe("stub", function () { }); describe("stubbed function", function () { - it("throws if stubbing non-existent property", function () { - var myObj = {}; - - assert.exception(function () { - createStub(myObj, "ouch"); - }); - - refute.defined(myObj.ouch); - }); - it("has toString method", function () { var obj = { meth: function () {} }; createStub(obj, "meth"); @@ -2301,4 +2281,86 @@ describe("stub", function () { assert.equals(reference, myObj); }); }); + + describe(".get", function () { + it("allows users to stub getter functions for properties", function () { + var myObj = { + prop: "foo" + }; + + createStub(myObj, "prop").get(function () { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + + it("replaces old getters", function () { + var myObj = { + get prop() { + fail("should not call the old getter"); + } + }; + + createStub(myObj, "prop").get(function () { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + + it("can set getters for non-existing properties", function () { + var myObj = {}; + + createStub(myObj, "prop").get(function () { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + }); + + describe(".set", function () { + it("allows users to stub setter functions for properties", function () { + var myObj = { + prop: "foo" + }; + + createStub(myObj, "prop").set(function () { + myObj.example = "bar"; + }); + + myObj.prop = "baz"; + + assert.equals(myObj.example, "bar"); + }); + + it("replaces old setters", function () { + var myObj = { // eslint-disable-line accessor-pairs + set prop(val) { + fail("should not call the old setter"); + } + }; + + createStub(myObj, "prop").set(function () { + myObj.example = "bar"; + }); + + myObj.prop = "foo"; + + assert.equals(myObj.example, "bar"); + }); + + it("can set setters for non-existing properties", function () { + var myObj = {}; + + createStub(myObj, "prop").set(function () { + myObj.example = "bar"; + }); + + myObj.prop = "foo"; + + assert.equals(myObj.example, "bar"); + }); + }); }); From 5bbfa515f393647f150f531503d485d02eb12666 Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Sat, 11 Mar 2017 11:52:14 -0300 Subject: [PATCH 2/5] Allow stubbing getters and setters for function properties --- lib/sinon/stub.js | 9 +++++---- test/stub-test.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/sinon/stub.js b/lib/sinon/stub.js index b0c346e0e..dbd024a35 100644 --- a/lib/sinon/stub.js +++ b/lib/sinon/stub.js @@ -41,14 +41,15 @@ function stub(object, property, descriptor) { return stub.create(); } + var s = stub.create(arity); + s.rootObj = object; + s.propName = property; + if (isStubbingNonFuncProperty) { - var s = stub.create(); - s.rootObj = object; - s.propName = property; return s; } - return wrapMethod(object, property, stub.create(arity)); + return wrapMethod(object, property, s); } stub.createStubInstance = function (constructor) { diff --git a/test/stub-test.js b/test/stub-test.js index faefa2705..24d8e38e2 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -2295,6 +2295,20 @@ describe("stub", function () { assert.equals(myObj.prop, "bar"); }); + it("allows users to stub getter functions for functions", function () { + var myObj = { + prop: function propGetter() { + return "foo"; + } + }; + + createStub(myObj, "prop").get(function () { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + it("replaces old getters", function () { var myObj = { get prop() { @@ -2335,6 +2349,22 @@ describe("stub", function () { assert.equals(myObj.example, "bar"); }); + it("allows users to stub setter functions for functions", function () { + var myObj = { + prop: function propSetter() { + return "foo"; + } + }; + + createStub(myObj, "prop").set(function () { + myObj.example = "bar"; + }); + + myObj.prop = "baz"; + + assert.equals(myObj.example, "bar"); + }); + it("replaces old setters", function () { var myObj = { // eslint-disable-line accessor-pairs set prop(val) { From e852f4fdf537b5f57450b617cf307714c367089a Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Sat, 11 Mar 2017 12:43:41 -0300 Subject: [PATCH 3/5] Add restore method for stubbed property descriptors --- lib/sinon/stub.js | 9 +++--- test/stub-test.js | 78 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 5 deletions(-) diff --git a/lib/sinon/stub.js b/lib/sinon/stub.js index dbd024a35..42c29c4f7 100644 --- a/lib/sinon/stub.js +++ b/lib/sinon/stub.js @@ -44,12 +44,11 @@ function stub(object, property, descriptor) { var s = stub.create(arity); s.rootObj = object; s.propName = property; + s.restore = function restore() { + Object.defineProperty(object, property, actualDescriptor); + }; - if (isStubbingNonFuncProperty) { - return s; - } - - return wrapMethod(object, property, s); + return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s); } stub.createStubInstance = function (constructor) { diff --git a/test/stub-test.js b/test/stub-test.js index 24d8e38e2..ba161fbcc 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -2332,6 +2332,44 @@ describe("stub", function () { assert.equals(myObj.prop, "bar"); }); + + it("can restore stubbed setters for functions", function () { + var propFn = function () { + return "bar"; + }; + + var myObj = { + prop: propFn + }; + + var stub = createStub(myObj, "prop"); + + stub.get(function () { + return "baz"; + }); + + stub.restore(); + + assert.equals(myObj.prop, propFn); + }); + + it("can restore stubbed getters for properties", function () { + var myObj = { + get prop() { + return "bar"; + } + }; + + var stub = createStub(myObj, "prop"); + + stub.get(function () { + return "baz"; + }); + + stub.restore(); + + assert.equals(myObj.prop, "bar"); + }); }); describe(".set", function () { @@ -2392,5 +2430,45 @@ describe("stub", function () { assert.equals(myObj.example, "bar"); }); + + it("can restore stubbed setters for functions", function () { + var propFn = function () { + return "bar"; + }; + + var myObj = { + prop: propFn + }; + + var stub = createStub(myObj, "prop"); + + stub.set(function () { + myObj.otherProp = "baz"; + }); + + stub.restore(); + + assert.equals(myObj.prop, propFn); + }); + + it("can restore stubbed setters for properties", function () { + var myObj = { // eslint-disable-line accessor-pairs + set prop(val) { + this.otherProp = "bar"; + return "bar"; + } + }; + + var stub = createStub(myObj, "prop"); + + stub.set(function () { + myObj.otherProp = "baz"; + }); + + stub.restore(); + + myObj.prop = "foo"; + assert.equals(myObj.otherProp, "bar"); + }); }); }); From eb7f90f85a5fbc61f687fd03755315676f7fe0db Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Sat, 11 Mar 2017 12:46:57 -0300 Subject: [PATCH 4/5] Add names to previously anonymous getter and setter test functions --- test/stub-test.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test/stub-test.js b/test/stub-test.js index ba161fbcc..0ae518ced 100644 --- a/test/stub-test.js +++ b/test/stub-test.js @@ -2288,7 +2288,7 @@ describe("stub", function () { prop: "foo" }; - createStub(myObj, "prop").get(function () { + createStub(myObj, "prop").get(function getterFn() { return "bar"; }); @@ -2302,7 +2302,7 @@ describe("stub", function () { } }; - createStub(myObj, "prop").get(function () { + createStub(myObj, "prop").get(function getterFn() { return "bar"; }); @@ -2316,7 +2316,7 @@ describe("stub", function () { } }; - createStub(myObj, "prop").get(function () { + createStub(myObj, "prop").get(function getterFn() { return "bar"; }); @@ -2326,7 +2326,7 @@ describe("stub", function () { it("can set getters for non-existing properties", function () { var myObj = {}; - createStub(myObj, "prop").get(function () { + createStub(myObj, "prop").get(function getterFn() { return "bar"; }); @@ -2334,7 +2334,7 @@ describe("stub", function () { }); it("can restore stubbed setters for functions", function () { - var propFn = function () { + var propFn = function propFn() { return "bar"; }; @@ -2344,7 +2344,7 @@ describe("stub", function () { var stub = createStub(myObj, "prop"); - stub.get(function () { + stub.get(function getterFn() { return "baz"; }); @@ -2362,7 +2362,7 @@ describe("stub", function () { var stub = createStub(myObj, "prop"); - stub.get(function () { + stub.get(function getterFn() { return "baz"; }); @@ -2378,7 +2378,7 @@ describe("stub", function () { prop: "foo" }; - createStub(myObj, "prop").set(function () { + createStub(myObj, "prop").set(function setterFn() { myObj.example = "bar"; }); @@ -2394,7 +2394,7 @@ describe("stub", function () { } }; - createStub(myObj, "prop").set(function () { + createStub(myObj, "prop").set(function setterFn() { myObj.example = "bar"; }); @@ -2410,7 +2410,7 @@ describe("stub", function () { } }; - createStub(myObj, "prop").set(function () { + createStub(myObj, "prop").set(function setterFn() { myObj.example = "bar"; }); @@ -2422,7 +2422,7 @@ describe("stub", function () { it("can set setters for non-existing properties", function () { var myObj = {}; - createStub(myObj, "prop").set(function () { + createStub(myObj, "prop").set(function setterFn() { myObj.example = "bar"; }); @@ -2432,7 +2432,7 @@ describe("stub", function () { }); it("can restore stubbed setters for functions", function () { - var propFn = function () { + var propFn = function propFn() { return "bar"; }; @@ -2442,7 +2442,7 @@ describe("stub", function () { var stub = createStub(myObj, "prop"); - stub.set(function () { + stub.set(function setterFn() { myObj.otherProp = "baz"; }); @@ -2461,7 +2461,7 @@ describe("stub", function () { var stub = createStub(myObj, "prop"); - stub.set(function () { + stub.set(function setterFn() { myObj.otherProp = "baz"; }); From ad584671cf2803829701472bd01a6efbb4b57260 Mon Sep 17 00:00:00 2001 From: lucasfcosta Date: Sat, 11 Mar 2017 13:10:20 -0300 Subject: [PATCH 5/5] Add docs for the new API get and set behaviors --- docs/release-source/release/stubs.md | 35 ++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/docs/release-source/release/stubs.md b/docs/release-source/release/stubs.md index 48c8031d8..70972fde3 100644 --- a/docs/release-source/release/stubs.md +++ b/docs/release-source/release/stubs.md @@ -448,3 +448,38 @@ var stub = sinon.stub().returnsNum(42); assert.equals(stub(), 42); ``` + +#### `stub.get(getterFn)` + +Replaces a new getter for this stub. + +```javascript +var myObj = { + prop: 'foo' +}; + +createStub(myObj, 'prop').get(function getterFn() { + return 'bar'; +}); + +myObj.example; // 'bar' +``` + +#### `stub.set(setterFn)` + +Defines a new setter for this stub. + +```javascript +var myObj = { + example: 'oldValue', + prop: 'foo' +}; + +createStub(myObj, 'prop').set(function setterFn(val) { + myObj.example = val; +}); + +myObj.prop = 'baz'; + +myObj.example; // 'baz' +```