Skip to content

Commit 66df977

Browse files
authored
Fix sinon.restore() cascade-restoring sub-sandboxes (#2704)
The ESM port of `createApi` (#2683, shipped in 21.1.0) replaced `createSandbox: createSandbox` with a wrapper that pushes every newly-created sandbox into the root sandbox's fake collection: createSandbox: function createSandbox(config) { const s = createConfiguredSandbox(config); sandbox.getFakes().push(s); return s; } `Sandbox#restore` then walks that collection and calls `.restore()` on each entry. Because a sub-sandbox is itself an entry, every top-level `sinon.restore()` cascades into every sub-sandbox and undoes its stubs/timers/etc. — defeating the whole point of having an isolated sub-sandbox. The same cascade hits `resetHistory` and `verifyAndRestore`. This is the regression reported in #2701. Restore the pre-21.1 behaviour: hand the root API a direct reference to `createConfiguredSandbox`. Sub-sandboxes are now isolated; only `subSandbox.restore()` (or `verifyAndRestore`) clears their fakes. Also flip the four sandbox tests that were locking in the buggy cascade: they now assert the parent's restore/resetHistory leaves the child untouched, with an explicit child-side cleanup at the end. Closes #2701
1 parent f0bd6e1 commit 66df977

3 files changed

Lines changed: 40 additions & 12 deletions

File tree

src/create-sinon-api.js

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ export default function createApi() {
2020
const sandbox = new Sandbox();
2121

2222
const apiMethods = {
23-
createSandbox: function createSandbox(config) {
24-
const s = createConfiguredSandbox(config);
25-
sandbox.getFakes().push(s);
26-
return s;
27-
},
23+
// `createSandbox` returns an isolated sandbox: its fakes are tracked
24+
// on its own collection so consumers can rely on `subSandbox.restore()`
25+
// (and only that) for cleanup. Don't push it into the global sandbox's
26+
// collection — doing so caused `sinon.restore()` to cascade-restore
27+
// sub-sandboxes (regression in 21.1.0, see #2701).
28+
createSandbox: createConfiguredSandbox,
2829
match: samsam.createMatcher,
2930
restoreObject: restoreObject,
3031
expectation: expectation,

test/src/create-sinon-api-test.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,26 @@ describe("create-sinon-api", function () {
1717
assert.isFunction(sinon.expectation.create);
1818
});
1919

20-
it("tracks created sandboxes in the root sandbox collection", function () {
20+
it("does not track created sandboxes in the root sandbox collection", function () {
21+
// Sub-sandboxes are isolated by design: pushing them into the root
22+
// sandbox's collection caused `sinon.restore()` to cascade-restore
23+
// sub-sandbox stubs (regression in 21.1.0, see #2701).
2124
const sinon = createApi();
2225
const fakes = sinon.getFakes();
2326
const sandbox = sinon.createSandbox();
2427

25-
assert.equals(fakes.indexOf(sandbox) !== -1, true);
28+
assert.equals(fakes.indexOf(sandbox), -1);
29+
});
30+
31+
it("does not restore sub-sandboxes when the root sandbox is restored", function () {
32+
const sinon = createApi();
33+
const sandbox = sinon.createSandbox();
34+
const target = { method: () => "original" };
35+
sandbox.stub(target, "method").returns("stubbed");
36+
37+
sinon.restore();
38+
39+
assert.equals(target.method(), "stubbed");
2640
});
2741

2842
it("tracks stub instance methods in the root sandbox collection", function () {

test/src/sandbox-test.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2048,7 +2048,11 @@ describe("Sandbox", function () {
20482048
});
20492049

20502050
describe("nested sandboxes", function () {
2051-
it("should restore nested sandboxes when the parent is restored", function () {
2051+
// Sub-sandboxes returned by `parent.createSandbox()` are isolated:
2052+
// the parent's `restore()` / `resetHistory()` / `verifyAndRestore()`
2053+
// must not touch the child's fakes or timers. Cascading was a
2054+
// regression in 21.1.0 (see #2701) and is now reverted.
2055+
it("should not restore nested sandboxes when the parent is restored", function () {
20522056
const parent = createApi(); // parent is a "sinon-like" sandbox
20532057
const child = parent.createSandbox();
20542058

@@ -2059,10 +2063,13 @@ describe("Sandbox", function () {
20592063

20602064
parent.restore();
20612065

2066+
assert.equals(myObj.method(), "stubbed");
2067+
2068+
child.restore();
20622069
assert.equals(myObj.method(), "original");
20632070
});
20642071

2065-
it("should reset history of nested sandboxes when the parent resetHistory is called", function () {
2072+
it("should not reset history of nested sandboxes when the parent resetHistory is called", function () {
20662073
const parent = createApi();
20672074
const child = parent.createSandbox();
20682075

@@ -2073,10 +2080,10 @@ describe("Sandbox", function () {
20732080

20742081
parent.resetHistory();
20752082

2076-
assert.isFalse(spy.called);
2083+
assert.isTrue(spy.calledOnce);
20772084
});
20782085

2079-
it("should restore nested sandboxes when the parent verifyAndRestore is called", function () {
2086+
it("should not restore nested sandboxes when the parent verifyAndRestore is called", function () {
20802087
const parent = createApi();
20812088
const child = parent.createSandbox();
20822089

@@ -2087,10 +2094,13 @@ describe("Sandbox", function () {
20872094

20882095
parent.verifyAndRestore();
20892096

2097+
assert.equals(myObj.method(), "stubbed");
2098+
2099+
child.restore();
20902100
assert.equals(myObj.method(), "original");
20912101
});
20922102

2093-
it("should restore fake timers when the parent sandbox is restored", function () {
2103+
it("should not restore fake timers when the parent sandbox is restored", function () {
20942104
const parent = createApi();
20952105
const child = parent.createSandbox();
20962106

@@ -2101,6 +2111,9 @@ describe("Sandbox", function () {
21012111

21022112
parent.restore();
21032113

2114+
refute.same(globalContext.Date, originalDate);
2115+
2116+
child.restore();
21042117
assert.same(globalContext.Date, originalDate);
21052118
});
21062119
});

0 commit comments

Comments
 (0)