-
-
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
sandbox.restore() does not work on static members of a function or class #2384
Comments
By the way: it is working with |
A static function is per def not part of that class. It is just a utility function tacked onto the class. class MyTest {}
MyTest.someStatic = function() { ... }
A static member of a class has no relationship with that class, apart from just being located there. (I did just browse very quickly through this, so maybe I read it wrong) EDIT: yes, I did read it wrong. I see I missed some details when skimming through it quickly. I somehow mixed this up a bit with |
OK, I have verified this using the example you posted. This does indeed look like a bug. Not sure why we treat a |
Function does not work either, as expected: const sinon = require('sinon')
const sandbox = sinon.createSandbox()
function MyTest() {}
MyTest.test = function (value) {
return value
}
sandbox.stub(MyTest)
sandbox.restore()
sandbox.stub(MyTest) // --> TypeError: Attempted to wrap test which is already wrapped |
this was not so straight forward after all ... we have different paths for objects and functions, so it got a bit hairy when I was looking at it. I'll continue another day when I have had some sleep :) A quickfix is to use //sandbox.restore();
sinon.restoreObject(MyTest); |
Thanks for your support, @fatso83 . For now I have a different workaround. The method you mentioned should not be used, as said in the docs:
And therefore it is also not supported by @types/sinon (which we use for typescript development). My workaround is to create an object for the stubbed Class by myself: let stubbedClass: SinonStubbedInstance<typeof MyClass>;
stubbedClass = {
doSomething: sandbox.stub(MyClass, 'doSomething'),
prototype: {},
}; when this issue is fixed i can replace it easily by |
@ghdoergeloh That same page documents exactly that method (second from the top), and it is both part of the exposed public API of the Your temporary fix is just as good, of course :-) |
Sorry, I totally misunderstood the sentence at the top of that page. (translation problems) |
I suspected as much after reading it myself, but feel free to improve it to make it less ambiguous: https://github.com/sinonjs/sinon/blob/master/docs/release-source/release/utils.md |
I'm glad that there's some workarounds, thanks for providing them! On a separate note, is this issue still considered a bug and therefore planned to be fixed at some point in the future? |
Hi, @marceliwac. Thanks for bumping this. I have forgotten all about it to be honest. When I looked at it now, I just saw the last part of the conversation, and thought it meant that whatever the issue was, it was fixed, but it is indeed a bug in the sandbox cleanup (as verified) and we should try and fix it. I removed myself as an assignee as I am a bit too caught up in other projects at the moment and I see that I was unable to fix this quickly the last time. cc @mroderick |
One more quick question regarding the above (might be separate issue but this conversation seems relevant). I'm having trouble spying on static properties of a class using the following: (Node 14 LTS) const sinon = require('sinon');
const assert = require('assert');
class C {
static f(x) {
return x+1;
}
}
const sandbox = sinon.createSandbox();
sandbox.spy(C);
C.f(1);
assert(C.f.calledWith(1)); The above code results in the following:
Does this have to do with the fact that static properties are somehow not bound to the class object or is this the problem we're discussing here as well (or a separate issue)? For reference, the following works fine: const sinon = require('sinon');
const assert = require('assert');
class C {
static f(x) {
return x+1;
}
}
const sandbox = sinon.createSandbox();
sandbox.spy(C, 'f');
C.f(1);
assert(C.f.calledWith(1)); |
@marceliwac There are two sides to your issue
The whyWhen one does /// spy.js
function createSpy(func){
function calledWith(...args){
for(let i = 0; i < this.calls; i++) {
if( deepEquals(this.arguments[i], args) ) return true;
}
return false;
}
return function spy(...args) {
spy.calls = spy.called? spy.calls + 1 : 1;
spy.arguments = spy.called? spy.arguments.push(args) : [args];
spy.called = true;
spy.calledWith =
return func(...args);
}
}
/// end spy.js (I just wrote this "blindfolded" now for pedagogical reasons, so it might not run without errors 😅) That means that if one did this: function myFunction(){
}
myFunction.staticMember = function(){}
const stubbedMyFunction = createSpy(myFunction); You will now have created a wrapper assert.isUndefined(stubbedMyFunction.staticMember); // true Relationship to classesSince the function C {}
C.f = function(x) {
return x+1;
} It's just like the plain jane function above, so any field member will not be copied. Bug or feature?This logic has been like this since Sinon's beginnings. One could argue that static class members are not related to the class at all, but merely a static utility function that should be handled standalone. On the other hand, it might break the expectations that tings should be auto-nice, just handling this for you. That would be a breaking change, but maybe one we want? That being said, it might not be super-easy to get right, seeing that fields come in all kinds of flavours, but should be doable. We would essentially need to loop over all the property descriptors (which we do already for objects, if my memory serves me right). If anyone would have a go at this, it's not hard, just a bit hairy with all the different paths 😄 I would suggest starting out with a test of what you want to end up with and then modify the library code until it passes.
|
@fatso83 Thanks for this comprehensive reply! The logic in your answer is sound and my problem comes down to my lack of understanding of ES6 Classes, which I thought would treated as objects and not functions i.e.: class C {
static f(x){
return x + 1;
}
}
typeof C === 'function' // true The reason why this was unexpected to me is this example from the documentation which operates on a normal object (could be instance of a class), which - as you said - does in fact loop over the property descriptors of the supplied object. In my mind, the ES6 class with its static fields would work just as well. I think it supporting the ES6 classes and their static properties in the same way objects are handled could be a useful feature to have and would be fairly achievable if there was an easy way to check whether an object is a class. Regardless of that, though, would you say it makes sense to clarify this distinction in documentation or is this generally obvious? |
I don't see why we should restrict ourselves to classes, per se. I think such a feature should work on any function as well, not just the subset that are constructor functions in ES2015+. Doing that, the need to check for serialized representations disappear and it will work just as well with code that has been transpiled down to ES5.1 (which does not have the The documentation could clearly need some clarification, so if you would like to fill in some details or a new section on stubbing constructors, feel free to do so. It's just markdown files in the same repo under |
Sounds great! I'll get on it at some point soon! |
I think this is releated, but |
Is this still relevant? If so, what is blocking it? Is there anything you can do to help move it forward? This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. |
Describe the bug
The documentation says, that we can stub all methods of a object by using
sinon.stub(obj)
https://sinonjs.org/releases/latest/stubs/#var-stub--sinonstubobj
This works great for objects and also for classes.
It also works for
sandbox.stub(obj)
What doesn't work, is to restore the methods of a class by using
sandbox.restore()
To Reproduce
This block works:
The output is:
This one doesn't:
The output is:
Expected behavior
It would be great if
sandbox.restore()
would also restore all methods of a class and not only of a object.Context (please complete the following information):
The text was updated successfully, but these errors were encountered: