Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

.define method #2539

Merged
merged 10 commits into from
Sep 11, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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`_
fatso83 marked this conversation as resolved.
Show resolved Hide resolved

#### `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 @@ -212,7 +216,7 @@ function Sandbox() {
const descriptor = getPropertyDescriptor(object, property);

function restorer() {
gukoff marked this conversation as resolved.
Show resolved Hide resolved
if (descriptor.isOwn) {
if (descriptor?.isOwn) {
Object.defineProperty(object, property, descriptor);
} else {
delete object[property];
Expand Down Expand Up @@ -244,7 +248,7 @@ function Sandbox() {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property
)}`
)}. Perhaps you meant sandbox.define()?`
);
}

Expand Down Expand Up @@ -278,6 +282,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(
gukoff marked this conversation as resolved.
Show resolved Hide resolved
property
)}. Perhaps you meant sandbox.replace()?`
);
}

if (typeof value === "undefined") {
throw new TypeError("Expected value argument to be defined");
}
gukoff marked this conversation as resolved.
Show resolved Hide resolved

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
83 changes: 82 additions & 1 deletion test/sandbox-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -807,11 +807,92 @@ 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 +954,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