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' +``` 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..42c29c4f7 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,27 +14,41 @@ 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); - } + var s = stub.create(arity); + s.rootObj = object; + s.propName = property; + s.restore = function restore() { + Object.defineProperty(object, property, actualDescriptor); + }; - return wrapMethod(object, property, stub.create(arity)); + return isStubbingNonFuncProperty ? s : wrapMethod(object, property, s); } stub.createStubInstance = function (constructor) { diff --git a/test/stub-test.js b/test/stub-test.js index 8660c9f5a..0ae518ced 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,194 @@ 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 getterFn() { + return "bar"; + }); + + 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 getterFn() { + 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 getterFn() { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + + it("can set getters for non-existing properties", function () { + var myObj = {}; + + createStub(myObj, "prop").get(function getterFn() { + return "bar"; + }); + + assert.equals(myObj.prop, "bar"); + }); + + it("can restore stubbed setters for functions", function () { + var propFn = function propFn() { + return "bar"; + }; + + var myObj = { + prop: propFn + }; + + var stub = createStub(myObj, "prop"); + + stub.get(function getterFn() { + 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 getterFn() { + return "baz"; + }); + + stub.restore(); + + 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 setterFn() { + myObj.example = "bar"; + }); + + myObj.prop = "baz"; + + 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 setterFn() { + 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 setterFn() { + 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 setterFn() { + myObj.example = "bar"; + }); + + myObj.prop = "foo"; + + assert.equals(myObj.example, "bar"); + }); + + it("can restore stubbed setters for functions", function () { + var propFn = function propFn() { + return "bar"; + }; + + var myObj = { + prop: propFn + }; + + var stub = createStub(myObj, "prop"); + + stub.set(function setterFn() { + 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 setterFn() { + myObj.otherProp = "baz"; + }); + + stub.restore(); + + myObj.prop = "foo"; + assert.equals(myObj.otherProp, "bar"); + }); + }); });