-
-
Notifications
You must be signed in to change notification settings - Fork 771
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
Using restore
after createStubInstance
creates frankenstein objects
#2477
Comments
Sorry for not having looked at this before, but after reading through this, I just want to note that this is an exemplary bug report. I think I understand the issue, but I need to have a look at the Props on the test suite btw; I haven't seen testing code for Angular before and I could totally understand it from the context, so very clean and understandable. |
OK, I think I have something that should have this work as you would expect:
- var stubbedObject = stub(Object.create(constructor.prototype));
+ // eslint-disable-next-line no-empty-function
+ const noop = () => {};
+ const defaultNoOpInstance = Object.create(constructor.prototype);
+ walkObject((obj, prop) => (obj[prop] = noop), defaultNoOpInstance);
+
+ const stubbedObject = stub(defaultNoOpInstance); This creates default no-op implementations of all the functions on the prototype, which will then be stubbed and subsequently restored. This should be the least surprising behavior, IMHO and according to your expectations? Given the following: $ cat test/test-stub-instance.js
"use strict";
const sinon = require("sinon");
class SystemUnderTest {
constructor() {
this.privateGetter = () => 42;
}
getValue() {
return this.privateGetter();
}
}
it("behavior of restored instance", function () {
const instance = sinon.createStubInstance(SystemUnderTest, {
getValue: "ooh, override of getValue",
});
console.log(instance.getValue());
sinon.restore();
console.log(instance.getValue()); // Existing behavior: TypeError: this.privateGetter is not a function
}); it will now run without failing, whereas previously it would throw after restoring. Before $ npx mocha test/test-stub-instance.js
ooh, override of getValue
1) behavior of restored instance
0 passing (4ms)
1 failing
1) behavior of restored instance:
TypeError: this.privateGetter is not a function
at SystemUnderTest.getValue (test/test-stub-instance.js:9:21)
at Context.<anonymous> (test/test-stub-instance.js:20:26)
at process.processImmediate (node:internal/timers:471:21) After $ npx mocha test/test-stub-instance.js
ooh, override of getValue
undefined
✔ behavior of restored instance
1 passing (5ms) |
ping @dpraul Is this what you want? Pushed the changes: https://github.com/sinonjs/sinon/compare/main...fatso83:sinon:issue-2477?expand=1 |
Thanks for the ping - I accidentally let this issue fall off my radar this week. And thanks as well for your kind words :) Your suggested behavior of restoring the stub to all no-ops makes sense to me. I patched your changes into our test suite and it ran without issue, and when I toggled on the Angular |
@mroderick OK with the changes? Not sure if this should be regarded as a breaking change or just a patch. The behavior of a restored stub instance has up until now not been defined in any docs, so it does not change any documented behavior. |
fix published as sinon 15.0.2 |
As I feared, this came back and bit us, unfortunately, as it makes Seems I might need to revert this 😢 Any ideas on how to get the best of both worlds without vastly complicating things? Right now, almost no logic, including cleanup, is specific to |
The fix to the bug I introduced by this is essentially moving the setting of the |
Describe the bug
Restoring stubs created by
createStubInstance
creates frankenstein objects that have the original prototype-chain of the object passed in without the object having been properly created through the constructor.To Reproduce
Here is a jsfiddle demonstrating the following code
Expected behavior
I'm not totally certain, but perhaps nothing? For us, the use-case of
createStubInstance
is to create drop-in objects that simulate the provided object's interface, but without ever touching its implementation. We use them in unit-testing to validate interactions. I'm not sure the use-case where you'd want a partial object that has not had its constructor run, so I cannot imagine this is intended behavior (and even in that case, I would expect to expose that behavior usingstub.callThrough()
, rather than by restoring the sandbox).Context
Additional Context
This behavior came to our attention as we're updating our application from Angular 13 to Angular 14. Angular 14 updates the default
TestBed
behavior to tear down the test module after each test. While this is arguably a good addition as it provides a cleaner testing environment for each test, it has brought these frankenstein objects to our attention.Since a sandox is created and restored inside of a test-suite, but the
TestBed
teardown occurs after the suite, theTestBed
still holds references to these partial objects. TheTestBed
cleanup triggers anyOnDestroy
methods on the objects, which may now have implementations associated with them.NOTE: This particular issue may be better-solved a multitude of ways (perhaps by introducing the TestBed teardown into the
afterEach
of the suite and prior to restoring the sandbox). Nonetheless, it seems like an issue that the implementations of these stubbed objects are being reintroduced back to the test suite, even after the suite has completed.Here is an example of this issue in our application
The text was updated successfully, but these errors were encountered: