Skip to content

Commit

Permalink
Move and reuse assertions
Browse files Browse the repository at this point in the history
  • Loading branch information
fatso83 committed Oct 3, 2023
1 parent e71b0c9 commit 93f8fe2
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 56 deletions.
1 change: 1 addition & 0 deletions docs/release-source/release/sandbox.md
Expand Up @@ -241,6 +241,7 @@ console.log(myObject.myMethod());
Usually one intends to _replace_ the value or getter of a field, but there are use cases where one actually wants to _assign_ a value to a property using an existing setter. `#replace.usingAccessor(object, property, value)` will do just that; pass the value into setter function and vice-versa use the getter to get the value used for restoring later on.

##### Use case: no-frills dependency injection in ESM with cleanup

One use case can be to conveniently allow ESM module stubbing using pure dependency injection, having Sinon help you with the cleanup, without resorting to external machinery such as module loaders or require hooks (see [#2403](https://github.com/sinonjs/sinon/issues/2403)). This would then work regardless of bundler, browser or server environment.

#### `sandbox.replaceGetter(object, property, replacementFunction);`
Expand Down
89 changes: 51 additions & 38 deletions lib/sinon/sandbox.js
Expand Up @@ -35,6 +35,40 @@ function applyOnEach(fakes, method) {
});
}

function throwOnAccessors(descriptor) {
if (typeof descriptor.get === "function") {
throw new Error("Use sandbox.replaceGetter for replacing getters");
}

if (typeof descriptor.set === "function") {
throw new Error("Use sandbox.replaceSetter for replacing setters");
}
}

function verifySameType(object, property, replacement) {
if (typeof object[property] !== typeof replacement) {
throw new TypeError(
`Cannot replace ${typeof object[
property
]} with ${typeof replacement}`
);
}
}

function checkForValidArguments(descriptor, property, replacement) {
if (typeof descriptor === "undefined") {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property
)}. Perhaps you meant sandbox.define()?`
);
}

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

function Sandbox() {
const sandbox = this;
let fakeRestorers = [];
Expand Down Expand Up @@ -239,59 +273,38 @@ function Sandbox() {
*/
sandbox.replace = function replace(object, property, replacement) {
const descriptor = getPropertyDescriptor(object, property);

if (typeof descriptor === "undefined") {
throw new TypeError(
`Cannot replace non-existent property ${valueToString(
property
)}. Perhaps you meant sandbox.define()?`
);
}

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

if (typeof descriptor.get === "function") {
throw new Error("Use sandbox.replaceGetter for replacing getters");
}

if (typeof descriptor.set === "function") {
throw new Error("Use sandbox.replaceSetter for replacing setters");
}

if (typeof object[property] !== typeof replacement) {
throw new TypeError(
`Cannot replace ${typeof object[
property
]} with ${typeof replacement}`
);
}
checkForValidArguments(descriptor, property, replacement);
throwOnAccessors(descriptor);
verifySameType(object, property, replacement);

verifyNotReplaced(object, property);

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

object[property] = replacement;

return replacement;
};

sandbox.replace.usingAccessor = function replaceUsingAccessor(object, property, replacement) {
sandbox.replace.usingAccessor = function replaceUsingAccessor(
object,
property,
replacement
) {
const descriptor = getPropertyDescriptor(object, property);
checkForValidArguments(descriptor, property, replacement);
verifySameType(object, property, replacement);

verifyNotReplaced(object, property);

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

object[property] = replacement;

return replacement;
}
};

sandbox.define = function define(object, property, value) {
const descriptor = getPropertyDescriptor(object, property);
Expand Down
38 changes: 20 additions & 18 deletions test/sandbox-test.js
Expand Up @@ -1114,26 +1114,28 @@ describe("Sandbox", function () {
}
);
});
});
});

it("should allow using assignment when replacing a value", function () {
const sandbox = this.sandbox;
let hiddenFoo = () => "original";
const object = {
// eslint-disable-next-line accessor-pairs
get foo() {
return hiddenFoo;
},
set foo(value) {
hiddenFoo = value;
},
};
describe(".replace.usingAccessor", function () {
it("should allow using assignment when replacing a value", function () {
const sandbox = createSandbox();
let quaziPrivateStateOfObject = "original";
const object = {
// eslint-disable-next-line accessor-pairs
get foo() {
return quaziPrivateStateOfObject;
},
set foo(value) {
quaziPrivateStateOfObject = value;
},
};

assert.equals(object.foo(), "original");
sandbox.replace.usingAccessor(object, "foo", sinonFake.returns("fake"));
assert.equals(object.foo(), "fake");
sandbox.restore();
assert.equals(object.foo(), "original");
});
assert.equals(object.foo, "original");
sandbox.replace.usingAccessor(object, "foo", "fake");
assert.equals(object.foo, "fake");
sandbox.restore();
assert.equals(object.foo, "original");
});
});

Expand Down

0 comments on commit 93f8fe2

Please sign in to comment.