Skip to content

Commit

Permalink
.define method (#2539)
Browse files Browse the repository at this point in the history
* Restore sandbox after each .replace test

Otherwise a failed test may not restore it.

* .define method for temporarily defining new properties during the tests

* better comment

* detailed exception messages

* properly delete the property during the cleanup

* Add .define to more places

* Document .define

* Fix test

* Code review suggestions

* prettier --write
  • Loading branch information
gukoff committed Sep 11, 2023
1 parent 2ddf7ff commit baa1aee
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 4 deletions.
36 changes: 35 additions & 1 deletion docs/release-source/release/sandbox.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ title: Sandboxes - Sinon.JS
breadcrumb: sandbox
---

Sandboxes removes the need to keep track of every fake created, which greatly simplifies cleanup.
Sandboxes remove the need to keep track of every fake created, which greatly simplifies cleanup.

```javascript
var sandbox = require("sinon").createSandbox();
Expand Down Expand Up @@ -181,6 +181,40 @@ A convenience reference for [`sinon.assert`](./assertions)

_Since `sinon@2.0.0`_

#### `sandbox.define(object, property, value);`

Defines the `property` on `object` with the value `value`. Attempts to define an already defined value cause an exception.

`value` can be any value except `undefined`, including `spies`, `stubs` and `fakes`.

```js
var myObject = {};

sandbox.define(myObject, "myValue", function () {
return "blackberry";
});

sandbox.define(myObject, "myMethod", function () {
return "strawberry";
});

console.log(myObject.myValue);
// blackberry

console.log(myObject.myMethod());
// strawberry

sandbox.restore();

console.log(myObject.myValue);
// undefined

console.log(myObject.myMethod);
// undefined
```

_Since `sinon@15.3.0`_

#### `sandbox.replace(object, property, replacement);`

Replaces `property` on `object` with `replacement` argument. Attempts to replace an already replaced value cause an exception. Returns the `replacement`.
Expand Down
33 changes: 31 additions & 2 deletions lib/sinon/sandbox.js
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ function Sandbox() {
return sandbox.fake.apply(null, arguments);
};

obj.define = function () {
return sandbox.define.apply(null, arguments);
};

obj.replace = function () {
return sandbox.replace.apply(null, arguments);
};
Expand Down Expand Up @@ -196,7 +200,7 @@ function Sandbox() {
const descriptor = getPropertyDescriptor(object, property);

function restorer() {
if (descriptor.isOwn) {
if (descriptor?.isOwn) {
Object.defineProperty(object, property, descriptor);
} else {
delete object[property];
Expand Down Expand Up @@ -228,7 +232,7 @@ function Sandbox() {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property
)}`
)}. Perhaps you meant sandbox.define()?`
);
}

Expand Down Expand Up @@ -262,6 +266,31 @@ function Sandbox() {
return replacement;
};

sandbox.define = function define(object, property, value) {
const descriptor = getPropertyDescriptor(object, property);

if (descriptor) {
throw new TypeError(
`Cannot define the already existing property ${valueToString(
property
)}. Perhaps you meant sandbox.replace()?`
);
}

if (typeof value === "undefined") {
throw new TypeError("Expected value argument to be defined");
}

verifyNotReplaced(object, property);

// store a function for restoring the defined property
push(fakeRestorers, getFakeRestorer(object, property));

object[property] = value;

return value;
};

sandbox.replaceGetter = function replaceGetter(
object,
property,
Expand Down
1 change: 1 addition & 0 deletions lib/sinon/util/core/default-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module.exports = {
"server",
"requests",
"fake",
"define",
"replace",
"replaceSetter",
"replaceGetter",
Expand Down
87 changes: 86 additions & 1 deletion test/sandbox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,96 @@ describe("Sandbox", function () {
});
});

describe(".define", function () {
beforeEach(function () {
this.sandbox = createSandbox();
});

afterEach(function () {
this.sandbox.restore();
});

it("should define a function property", function () {
function newFunction() {
return;
}

const object = {};

this.sandbox.define(object, "property", newFunction);

assert.equals(object.property, newFunction);

this.sandbox.restore();

assert.isUndefined(object.property);
});

it("should define a non-function property", function () {
const newValue = "some-new-value";
const object = {};

this.sandbox.define(object, "property", newValue);

assert.equals(object.property, newValue);

this.sandbox.restore();

assert.isUndefined(object.property);
});

it("should error on existing descriptor", function () {
const sandbox = this.sandbox;

const existingValue = "123";
const existingFunction = () => "abcd";

const object = {
existingValue: existingValue,
existingFunction: existingFunction,
};

assert.exception(
function () {
sandbox.define(object, "existingValue", "new value");
},
{
message:
"Cannot define the already existing property existingValue. Perhaps you meant sandbox.replace()?",
name: "TypeError",
}
);

assert.exception(
function () {
sandbox.define(
object,
"existingFunction",
() => "new function"
);
},
{
message:
"Cannot define the already existing property existingFunction. Perhaps you meant sandbox.replace()?",
name: "TypeError",
}
);

// Verify that the methods above, even though they failed, did not replace the values
assert.equals(object.existingValue, existingValue);
assert.equals(object.existingFunction, existingFunction);
});
});

describe(".replace", function () {
beforeEach(function () {
this.sandbox = createSandbox();
});

afterEach(function () {
this.sandbox.restore();
});

it("should replace a function property", function () {
const replacement = function replacement() {
return;
Expand Down Expand Up @@ -873,7 +958,7 @@ describe("Sandbox", function () {
},
{
message:
"Cannot replace non-existent property i-dont-exist",
"Cannot replace non-existent property i-dont-exist. Perhaps you meant sandbox.define()?",
name: "TypeError",
}
);
Expand Down

0 comments on commit baa1aee

Please sign in to comment.