diff --git a/lib/sinon/behavior.js b/lib/sinon/behavior.js index c6b3653de..9d73cc2b1 100644 --- a/lib/sinon/behavior.js +++ b/lib/sinon/behavior.js @@ -350,6 +350,26 @@ var proto = { return this.chain(); }, + get: function get(getterFunction) { + var rootStub = this.stub || this; + + Object.defineProperty(rootStub.rootObj, rootStub.propName, { + get: getterFunction + }); + + return this.chain(); + }, + + set: function set(setterFunction) { + var rootStub = this.stub || this; + + Object.defineProperty(rootStub.rootObj, rootStub.propName, { // eslint-disable-line accessor-pairs + set: setterFunction + }); + + return this.chain(); + }, + chain: function chain() { /** * "this" is stub when method is called directly on stub, e.g. stub.returns(123); diff --git a/lib/sinon/stub.js b/lib/sinon/stub.js index 79e23a652..0e6ef01df 100644 --- a/lib/sinon/stub.js +++ b/lib/sinon/stub.js @@ -4,6 +4,7 @@ var behavior = require("./behavior"); 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"); @@ -12,24 +13,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"); + }); + }); });