Skip to content

Commit f4f7d93

Browse files
authored
Perform additional cleanup when calling callThrough() (#2670)
1 parent 6199e9e commit f4f7d93

2 files changed

Lines changed: 372 additions & 0 deletions

File tree

src/sinon/default-behaviors.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,10 +241,40 @@ const defaultBehaviors = {
241241

242242
callThrough: function callThrough(fake) {
243243
fake.callsThrough = true;
244+
245+
fake.callArgAt = undefined;
246+
fake.callsThroughWithNew = false;
247+
fake.exception = undefined;
248+
fake.exceptionCreator = undefined;
249+
fake.fakeFn = undefined;
250+
fake.reject = false;
251+
fake.resolve = false;
252+
fake.resolveArgAt = undefined;
253+
fake.resolveThis = false;
254+
fake.returnArgAt = undefined;
255+
fake.returnThis = false;
256+
fake.returnValue = undefined;
257+
fake.throwArgAt = undefined;
258+
259+
fake.callArgProp = undefined;
260+
fake.callbackArguments = [];
261+
fake.callbackContext = undefined;
262+
fake.callbackAsync = false;
263+
fake.returnValueDefined = false;
244264
},
245265

246266
callThroughWithNew: function callThroughWithNew(fake) {
247267
fake.callsThroughWithNew = true;
268+
269+
fake.callArgAt = undefined;
270+
fake.exception = undefined;
271+
fake.exceptionCreator = undefined;
272+
fake.throwArgAt = undefined;
273+
274+
fake.callArgProp = undefined;
275+
fake.callbackArguments = [];
276+
fake.callbackContext = undefined;
277+
fake.callbackAsync = false;
248278
},
249279

250280
get: function get(fake, getterFunction) {

test/issues/issues-test.js

Lines changed: 342 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -861,6 +861,348 @@ describe("issues", function () {
861861
assert.equals(cb.firstCall.args, ["Hello"]);
862862
});
863863

864+
describe("#2668 - callThrough", function () {
865+
describe("callThrough", function () {
866+
it("should clear throws(exception)", function () {
867+
const instance = {
868+
foo() {
869+
return "ok";
870+
},
871+
};
872+
873+
const stub = sinon.stub(instance, "foo");
874+
const expectedError = new Error("boom");
875+
stub.throws(expectedError);
876+
877+
assert.exception(
878+
() => instance.foo(),
879+
(err) => err === expectedError,
880+
);
881+
882+
stub.callThrough();
883+
884+
// Issue caused the stub to still apply.
885+
assert.equals(instance.foo(), "ok");
886+
});
887+
888+
it("should clear throws(exceptionFactory)", function () {
889+
const instance = {
890+
foo() {
891+
return "ok";
892+
},
893+
};
894+
895+
const stub = sinon.stub(instance, "foo");
896+
const expectedError = new Error("boom");
897+
stub.throws(function () {
898+
throw expectedError;
899+
});
900+
901+
assert.exception(
902+
() => instance.foo(),
903+
(err) => err === expectedError,
904+
);
905+
906+
stub.callThrough();
907+
908+
// Issue caused the stub to still apply.
909+
assert.equals(instance.foo(), "ok");
910+
});
911+
912+
it("should clear throwsArg", function () {
913+
const instance = {
914+
foo() {
915+
return "ok";
916+
},
917+
};
918+
919+
const stub = sinon.stub(instance, "foo");
920+
stub.throwsArg(0);
921+
922+
const expectedError = new Error("boom");
923+
assert.exception(
924+
() => instance.foo(expectedError),
925+
(err) => err === expectedError,
926+
);
927+
928+
stub.callThrough();
929+
930+
// Issue caused the stub to still apply.
931+
assert.equals(
932+
instance.foo(new Error("Should not be thrown")),
933+
"ok",
934+
);
935+
});
936+
937+
it("should clear returnsThis", function () {
938+
let called = false;
939+
const instance = {
940+
foo() {
941+
called = true;
942+
return this;
943+
},
944+
};
945+
946+
const stub = sinon.stub(instance, "foo");
947+
stub.returnsThis();
948+
949+
assert.equals(instance.foo(), instance);
950+
assert.equals(called, false);
951+
952+
stub.callThrough();
953+
954+
// Issue caused the stub to still apply.
955+
assert.same(instance.foo(), instance);
956+
assert.equals(called, true);
957+
});
958+
959+
it("should clear resolves", async function () {
960+
const instance = {
961+
foo() {
962+
return Promise.resolve("bar");
963+
},
964+
};
965+
966+
const stub = sinon.stub(instance, "foo");
967+
stub.resolves("baz");
968+
969+
assert.equals(await instance.foo(), "baz");
970+
971+
stub.callThrough();
972+
973+
// Issue caused the stub to still apply.
974+
assert.equals(await instance.foo(), "bar");
975+
});
976+
977+
it("should clear resolvesArg", async function () {
978+
const instance = {
979+
foo(a) {
980+
return Promise.resolve(a);
981+
},
982+
};
983+
984+
const stub = sinon.stub(instance, "foo");
985+
stub.resolvesArg(1);
986+
987+
assert.equals(await instance.foo("a", "b"), "b");
988+
989+
stub.callThrough();
990+
991+
// Issue caused the stub to still apply.
992+
assert.equals(await instance.foo("a", "b"), "a");
993+
});
994+
995+
it("should clear resolvesThis", async function () {
996+
const instance = {
997+
callCount: 0,
998+
foo() {
999+
this.callCount += 1;
1000+
return Promise.resolve(this);
1001+
},
1002+
};
1003+
1004+
const stub = sinon.stub(instance, "foo");
1005+
stub.resolvesThis();
1006+
1007+
assert.same(await instance.foo(), instance);
1008+
assert.equals(instance.callCount, 0);
1009+
1010+
stub.callThrough();
1011+
1012+
// Issue caused the stub to still apply.
1013+
assert.same(await instance.foo(), instance);
1014+
assert.equals(instance.callCount, 1);
1015+
});
1016+
1017+
it("should clear rejects", async function () {
1018+
const instance = {
1019+
foo() {
1020+
return Promise.resolve("ok");
1021+
},
1022+
};
1023+
1024+
const stub = sinon.stub(instance, "foo");
1025+
const error = new Error("nope");
1026+
stub.rejects(error);
1027+
1028+
await assert.rejects(instance.foo(), error);
1029+
1030+
stub.callThrough();
1031+
1032+
// Issue caused the stub to still apply.
1033+
assert.equals(await instance.foo(), "ok");
1034+
});
1035+
1036+
it("should clear returnsArg", function () {
1037+
const instance = {
1038+
foo(a) {
1039+
return `ok ${a}`;
1040+
},
1041+
};
1042+
1043+
const stub = sinon.stub(instance, "foo");
1044+
stub.returnsArg(0);
1045+
1046+
assert.equals(instance.foo("x"), "x");
1047+
1048+
stub.callThrough();
1049+
1050+
// Issue caused the stub to still apply.
1051+
assert.equals(instance.foo("z"), "ok z");
1052+
});
1053+
1054+
it("should clear callsFake", function () {
1055+
const instance = {
1056+
foo() {
1057+
return "orig";
1058+
},
1059+
};
1060+
1061+
const stub = sinon.stub(instance, "foo");
1062+
stub.callsFake(function () {
1063+
return "fake";
1064+
});
1065+
1066+
assert.equals(instance.foo(), "fake");
1067+
1068+
stub.callThrough();
1069+
1070+
// Issue caused the stub to still apply.
1071+
assert.equals(instance.foo(), "orig");
1072+
});
1073+
1074+
it("should clear callsArg/yield stuff", function () {
1075+
const instance = {
1076+
foo(cbk) {
1077+
cbk();
1078+
return "original";
1079+
},
1080+
};
1081+
1082+
const stub = sinon.stub(instance, "foo");
1083+
stub.callsArg(0);
1084+
stub.returns("fake");
1085+
1086+
const cbkStub = sinon.stub();
1087+
assert.same(instance.foo(cbkStub), "fake");
1088+
assert.equals(cbkStub.callCount, 1);
1089+
1090+
stub.callThrough();
1091+
1092+
const cbkStub2 = sinon.stub();
1093+
assert.same(instance.foo(cbkStub2), "original");
1094+
// Issue was causing the callback to be called once by the function, and once by the stub.
1095+
assert.equals(cbkStub2.callCount, 1);
1096+
});
1097+
});
1098+
1099+
describe("callThroughWithNew", function () {
1100+
it("should clear throws(exception)", function () {
1101+
class OriginalClass {
1102+
constructor() {
1103+
this.foo = "original";
1104+
}
1105+
}
1106+
1107+
const instance = { MyClass: OriginalClass };
1108+
1109+
const stub = sinon.stub(instance, "MyClass");
1110+
const expectedError = new Error("boom");
1111+
stub.throws(expectedError);
1112+
assert.exception(
1113+
() => new instance.MyClass(),
1114+
(err) => err === expectedError,
1115+
);
1116+
1117+
stub.callThroughWithNew();
1118+
1119+
// Issue caused the stub to still apply.
1120+
const obj = new instance.MyClass();
1121+
assert.equals(obj.foo, "original");
1122+
});
1123+
1124+
it("should clear throws(exceptionFactory)", function () {
1125+
class OriginalClass {
1126+
constructor() {
1127+
this.foo = "original";
1128+
}
1129+
}
1130+
1131+
const instance = { MyClass: OriginalClass };
1132+
1133+
const stub = sinon.stub(instance, "MyClass");
1134+
const expectedError = new Error("boom");
1135+
stub.throws(function () {
1136+
throw expectedError;
1137+
});
1138+
assert.exception(
1139+
() => new instance.MyClass(),
1140+
(err) => err === expectedError,
1141+
);
1142+
1143+
stub.callThroughWithNew();
1144+
1145+
// Issue caused the stub to still apply.
1146+
const obj = new instance.MyClass();
1147+
assert.equals(obj.foo, "original");
1148+
});
1149+
1150+
it("should clear throwsArg", function () {
1151+
class OriginalClass {
1152+
constructor() {
1153+
this.foo = "original";
1154+
}
1155+
}
1156+
1157+
const instance = { MyClass: OriginalClass };
1158+
1159+
const stub = sinon.stub(instance, "MyClass");
1160+
stub.throwsArg(0);
1161+
const expectedError = new Error("boom");
1162+
assert.exception(
1163+
() => new instance.MyClass(expectedError),
1164+
(err) => err === expectedError,
1165+
);
1166+
1167+
stub.callThroughWithNew();
1168+
1169+
// Issue caused the stub to still apply.
1170+
assert.equals(
1171+
new instance.MyClass(new Error("Should not be thrown")).foo,
1172+
"original",
1173+
);
1174+
});
1175+
1176+
it("should clear callsArg/yield stuff", function () {
1177+
class OriginalClass {
1178+
constructor(cbk) {
1179+
this.foo = "original";
1180+
cbk();
1181+
}
1182+
}
1183+
1184+
const instance = { MyClass: OriginalClass };
1185+
1186+
const stub = sinon.stub(instance, "MyClass");
1187+
1188+
stub.callsArg(0);
1189+
const dummyInstance = { foo: "fake" };
1190+
stub.returns(dummyInstance);
1191+
1192+
const cbkStub = sinon.stub();
1193+
assert.same(new instance.MyClass(cbkStub), dummyInstance);
1194+
assert.equals(cbkStub.callCount, 1);
1195+
1196+
stub.callThroughWithNew();
1197+
1198+
const cbkStub2 = sinon.stub();
1199+
assert.same(new instance.MyClass(cbkStub2).foo, "original");
1200+
// Issue was causing the callback to be called once by the constructor, and once by the stub.
1201+
assert.equals(cbkStub2.callCount, 1);
1202+
});
1203+
});
1204+
});
1205+
8641206
it("#2702 - stub creation should ignore inherited non-writable Object prototype properties", function () {
8651207
const descriptor = Object.getOwnPropertyDescriptor(
8661208
Object.prototype,

0 commit comments

Comments
 (0)