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"); + }); + }); });