Skip to content

Commit

Permalink
Merge pull request #1416 from lucasfcosta/refactor-sandbox-stub
Browse files Browse the repository at this point in the history
Refactor sandbox and allow it to stub getters and setters
  • Loading branch information
fatso83 committed May 22, 2017
2 parents fb514cf + 46fd81b commit 55dc491
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 9 deletions.
2 changes: 1 addition & 1 deletion docs/release-source/release/sandbox.md
Expand Up @@ -106,7 +106,7 @@ Works exactly like `sinon.spy`, only also adds the returned spy to the internal

Works almost exactly like `sinon.stub`, only also adds the returned stub to the internal collection of fakes for easy restoring through `sandbox.restore()`.

The sandbox `stub` method can also be used to stub any kind of property. This is useful if you need to override an object's property for the duration of a test, and have it restored when the test completes
The sandbox `stub` method can also be used to stub any kind of property. This is useful if you need to override an object's property for the duration of a test, and have it restored when the test completes.

#### `sandbox.mock();`

Expand Down
28 changes: 28 additions & 0 deletions docs/release-source/release/stubs.md
Expand Up @@ -520,3 +520,31 @@ myObj.prop = 'baz';

myObj.example; // 'baz'
```

#### `stub.value(newVal)`

Defines a new value for this stub.

```javascript
var myObj = {
example: 'oldValue',
};

sinon.stub(myObj, 'example').value('newValue');

myObj.example; // 'newValue'
```

You can restore values by calling the `restore` method:

```javascript
var myObj = {
example: 'oldValue',
};

var stub = sinon.stub(myObj, 'example').value('newValue');
stub.restore()

myObj.example; // 'oldValue'
```

12 changes: 5 additions & 7 deletions lib/sinon/collection.js
Expand Up @@ -3,9 +3,8 @@
var sinonSpy = require("./spy");
var sinonStub = require("./stub");
var sinonMock = require("./mock");
var throwOnFalsyObject = require("./throw-on-falsy-object");
var sandboxStub = require("./sandbox-stub");
var collectOwnMethods = require("./collect-own-methods");
var stubNonFunctionProperty = require("./stub-non-function-property");

var push = [].push;

Expand Down Expand Up @@ -81,13 +80,12 @@ var collection = {
},

stub: function stub(object, property/*, value*/) {
throwOnFalsyObject.apply(null, arguments);
if (arguments.length > 2) {
return sandboxStub.apply(this, arguments);
}

var stubbed = sinonStub.apply(null, arguments);
var isStubbingEntireObject = typeof property === "undefined" && typeof object === "object";
var isStubbingNonFunctionProperty = property && typeof object[property] !== "function";
var stubbed = isStubbingNonFunctionProperty ?
stubNonFunctionProperty.apply(null, arguments) :
sinonStub.apply(null, arguments);

if (isStubbingEntireObject) {
var ownMethods = collectOwnMethods(stubbed);
Expand Down
19 changes: 19 additions & 0 deletions lib/sinon/default-behaviors.js
@@ -1,4 +1,5 @@
"use strict";
var getPropertyDescriptor = require("./util/core/get-property-descriptor");

var slice = [].slice;
var useLeftMostCallback = -1;
Expand Down Expand Up @@ -194,6 +195,24 @@ module.exports = {
set: setterFunction
});

return fake;
},

value: function value(fake, newVal) {
var rootStub = fake.stub || fake;

var oldVal = getPropertyDescriptor(rootStub.rootObj, rootStub.propName).value;

Object.defineProperty(rootStub.rootObj, rootStub.propName, {
value: newVal
});

fake.restore = function restore() {
Object.defineProperty(rootStub.rootObj, rootStub.propName, {
value: oldVal
});
};

return fake;
}
};
Expand Down
51 changes: 51 additions & 0 deletions lib/sinon/sandbox-stub.js
@@ -0,0 +1,51 @@
"use strict";

var collectOwnMethods = require("./collect-own-methods");
var deprecated = require("./util/core/deprecated");
var getPropertyDescriptor = require("./util/core/get-property-descriptor");
var stubNonFunctionProperty = require("./stub-non-function-property");
var sinonStub = require("./stub");
var throwOnFalsyObject = require("./throw-on-falsy-object");

// This is deprecated and will be removed in a future version of sinon.
// We will only consider pull requests that fix serious bugs in the implementation
function sandboxStub(object, property/*, value*/) {
deprecated.printWarning(
"sandbox.stub(obj, 'meth', val) is deprecated and will be removed from " +
"the public API in a future version of sinon." +
"\n Use sandbox(obj, 'meth').callsFake(fn) instead in order to stub a function." +
"\n Use sandbox(obj, 'meth').value(fn) instead in order to stub a non-function value."
);

throwOnFalsyObject.apply(null, arguments);

var actualDescriptor = getPropertyDescriptor(object, property);
var isStubbingEntireObject = typeof property === "undefined" && typeof object === "object";
var isStubbingNonFuncProperty = typeof object === "object"
&& typeof property !== "undefined"
&& (typeof actualDescriptor === "undefined"
|| typeof actualDescriptor.value !== "function");


// When passing a value as third argument it will be applied to stubNonFunctionProperty
var stubbed = isStubbingNonFuncProperty ?
stubNonFunctionProperty.apply(null, arguments) :
sinonStub.apply(null, arguments);

if (isStubbingEntireObject) {
var ownMethods = collectOwnMethods(stubbed);
ownMethods.forEach(this.add.bind(this));
if (this.promiseLibrary) {
ownMethods.forEach(this.addUsingPromise.bind(this));
}
} else {
this.add(stubbed);
if (this.promiseLibrary) {
stubbed.usingPromise(this.promiseLibrary);
}
}

return stubbed;
}

module.exports = sandboxStub;
2 changes: 1 addition & 1 deletion lib/sinon/stub-non-function-property.js
Expand Up @@ -13,7 +13,7 @@ function stubNonFunctionProperty(object, property, value) {
object[property] = value;

return {
restore: function () {
restore: function restore() {
object[property] = original;
}
};
Expand Down
26 changes: 26 additions & 0 deletions test/collection-test.js
Expand Up @@ -5,6 +5,7 @@ var sinonCollection = require("../lib/sinon/collection");
var sinonSpy = require("../lib/sinon/spy");
var sinonStub = require("../lib/sinon/stub");
var assert = referee.assert;
var deprecated = require("../lib/sinon/util/core/deprecated");

describe("collection", function () {
it("creates fake collection", function () {
Expand Down Expand Up @@ -106,8 +107,13 @@ describe("collection", function () {
});

it("stubs environment property", function () {
var originalPrintWarning = deprecated.printWarning;
deprecated.printWarning = function () {};

this.collection.stub(process.env, "HELL", "froze over");
assert.equals(process.env.HELL, "froze over");

deprecated.printWarning = originalPrintWarning;
});
});
}
Expand All @@ -120,37 +126,57 @@ describe("collection", function () {
});

it("stubs number property", function () {
var originalPrintWarning = deprecated.printWarning;
deprecated.printWarning = function () {};

this.collection.stub(this.object, "property", 1);

assert.equals(this.object.property, 1);

deprecated.printWarning = originalPrintWarning;
});

it("restores number property", function () {
var originalPrintWarning = deprecated.printWarning;
deprecated.printWarning = function () {};

this.collection.stub(this.object, "property", 1);
this.collection.restore();

assert.equals(this.object.property, 42);

deprecated.printWarning = originalPrintWarning;
});

it("fails if property does not exist", function () {
var originalPrintWarning = deprecated.printWarning;
deprecated.printWarning = function () {};

var collection = this.collection;
var object = {};

assert.exception(function () {
collection.stub(object, "prop", 1);
});

deprecated.printWarning = originalPrintWarning;
});

it("fails if Symbol does not exist", function () {
if (typeof Symbol === "function") {
var collection = this.collection;
var object = {};

var originalPrintWarning = deprecated.printWarning;
deprecated.printWarning = function () {};

assert.exception(function () {
collection.stub(object, Symbol(), 1);
}, function (err) {
return err.message === "Cannot stub non-existent own property Symbol()";
});

deprecated.printWarning = originalPrintWarning;
}
});
});
Expand Down
62 changes: 62 additions & 0 deletions test/sandbox-test.js
Expand Up @@ -548,4 +548,66 @@ describe("sinonSandbox", function () {
sandbox.restore();
});
});

describe("getters and setters", function () {
it("allows stubbing getters", function () {
var object = {
foo: "bar"
};

var sandbox = sinonSandbox.create();
sandbox.stub(object, "foo").get(function () {
return "baz";
});

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

it("allows restoring getters", function () {
var object = {
foo: "bar"
};

var sandbox = sinonSandbox.create();
sandbox.stub(object, "foo").get(function () {
return "baz";
});

sandbox.restore();

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

it("allows stubbing setters", function () {
var object = {
prop: "bar"
};

var sandbox = sinonSandbox.create();
sandbox.stub(object, "foo").set(function (val) {
object.prop = val + "bla";
});

object.foo = "bla";

assert.equals(object.prop, "blabla");
});

it("allows restoring setters", function () {
var object = {
prop: "bar"
};

var sandbox = sinonSandbox.create();
sandbox.stub(object, "prop").set(function setterFn(val) {
object.prop = val + "bla";
});

sandbox.restore();

object.prop = "bla";

assert.equals(object.prop, "bla");
});
});
});
22 changes: 22 additions & 0 deletions test/stub-test.js
Expand Up @@ -2620,4 +2620,26 @@ describe("stub", function () {
assert.equals(myObj.otherProp, "bar");
});
});

describe(".value", function () {
it("allows stubbing property descriptor values", function () {
var myObj = {
prop: "rawString"
};

createStub(myObj, "prop").value("newString");
assert.equals(myObj.prop, "newString");
});

it("allows restoring stubbed property descriptor values", function () {
var myObj = {
prop: "rawString"
};

var stub = createStub(myObj, "prop").value("newString");
stub.restore();

assert.equals(myObj.prop, "rawString");
});
});
});

0 comments on commit 55dc491

Please sign in to comment.