-
-
Notifications
You must be signed in to change notification settings - Fork 769
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
Cannot spy on property getters, and stubbed getters do not record calls #1741
Comments
Thank you for your contribution! This is a curious issue. I too was under the impression that calls were being recorded, but something seems amiss. Your example shows the issue clearly 👍 I think this one will require some digging to correctly diagnose it. A good start would be to use |
@mroderick, I can help out with this. I'll take a look, as you suggest, to see if there was previous support for this. |
That would be great, thank you! |
Support for getters + setters was introduced in commit 278c2ce, just before the v2.0.0 release. The Prior to that commit, attempts to stub out a property were met with Property getter + setter stubbing was introduced in Sinon v1.14.0, in commit bb467e6 and was fixed up in commit 8dace57 for v1.17. That facility was lost sometime between there and v2, but I haven't tracked that down. @lucasfcosta previously mentioned the lack of recording ability in issue #1392, but that was closed after a while, with no changes made. The API approach taken with v1 was that the user specifically requested, at stubbing time, whether get+set were to be stubbed. The API approach with v2 is that extra It would be great to have both stubbing and spying supported on getters and setters, but it would probably require additional API. I can see a couple of options initially:
|
Any opinions on the two API options I suggested, or any other suggestions? |
Meantime, for those looking for a workaround, this is possible with a combination of stub.get/set + spy: #1545 comment-385262212 |
@RoystonS If pressed, I would go for #1, but it does pave the way for (even more) confused questions on why things don't act the expected way. This is due to how fluent interfaces often end up with confusing behaviours. In #1545 I mentioned an example with My current take (not set in stone) is that what we in general lack the most are good examples on how to do stuff. For instance, spying on property getters/setters is perfectly doable today, as I suggested in #1545 (pass in a spy as the getterfn), but as this issue shows that knowledge is too hidden away for anyone to find it. |
There is, btw, another take on the API that wouldn't change existing code
spy.getter.calledOnce, etc |
PS. This issue isn't a bug, as it's willed, intended behaviour, though poorly documented. Previous volunteers for documenting it never got back with a PR .. As it's not a bug, but a feature request in disguise, I am tempted to close it, but maybe we should just change the title and label it as a feature request to save the noise of creating (yet) another issue? |
@fatso83 that's exactly what I would expect (and was the first or second thing I originally tried). More robust examples in the docs would be very appreciated. I get a lot of questions from my colleagues that could be answered by that. I'd be happy to contribute to that. |
It's clear we need to look at property accessors again (see also #1762 for a discussion on proposals to API changes and why there's no clear "best" path), but having more docs is always welcome. Feel free to provide us with what you would have liked to be in the how-to section. |
@fatso83 the description for Stubs is confusing:
When first starting with Sinon, I, and several others, understood that to mean that the behaviour was already set, not that the implementer/developer determines the behaviour (or doesn't). That's also not entirely accurate, because if I don't attach any behaviours to the stub (via Some Getting Started documentation would be very appreciated by newcomers. Ex I didn't realise how incredibly useful Some recommended approaches and tips would also be great. Ex Spying on getters and setters of a stubbed object is quite difficult to figure out (once you figure it out, it makes sense, but before, it's a pretty big wtf). The last thing I can think of are examples that are less abstract and more practical/real-world to help the reader understand how/when a particular method is useful. Ex The appropriate use-case for So a warning would be nice:
Potential real-world example for the docs: const fetchStub = sandbox.stub(window, 'fetch');
fetchStub
.resolves(optionalResponse);
fetchStub
.withArgs('/a/bad/path').rejects(optionalResponse);
window.fetch('/a/good/path'); // resolves
window.fetch('/a/bad/path'); // rejects
fetchStub
.withArgs('/a/bad/path').resolves(optionalResponse); // overwrites previous behaviour
window.fetch('/a/bad/path'); // now resolves
window.fetch('/a/good/path'); // unchanged: still resolves |
An official "Getting started" would be great and is sorely needed. Right now, there are lots intros on the web, but no one that we control and officially support, although the Articles elsewhere on the web are some of the ones we recommend. |
@fatso83 i happen to have already written one for my department. You're welcome to have it if you'd like. It's currently in slide-form. I can convert it to markdown or whatever. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
@fatso83 did you want that getting started stuff? I'd happily lend a hand. |
@jshado1 that would be great, thank you |
Just wanted to throw in my /2c+1 on the 'unexpected effect of getters not being spies', would love to see this feature included, though @jshado1 's workaround is also how I was thinking of solving it in the interim. Though currently, not sure how to get it working. A cut down example:
I would expect I should be able to do the following:
But that returns Looking at the
My workaround: - const task = mockTask()
+ const task = { ...mockTask(), __isRunningStub: isRunningStub } - t.is(mockedTask.isRunning.callCount, 1)
+ t.is(mockedTask.__isRunningStub.callCount, 1) |
@0xdevalias Not trying to sell you short, but we are trying to keep the GitHub issues list tidy and focused on bugs and feature discussions, and your looks like a better fit forStackOverflow, as there are people literally waiting for someone to tag it with |
Been away from this one for a while as I mostly just got used to the limitation. I can take a look at implementing the suggestion in #1741 (comment) if somebody (@fatso83 ?) would be willing to give a hand with reviewing? |
@RoystonS This is my second check-in since April (parental leave), so I am not that guy ATM ... |
Hello I found that in the wrap-mothod.js file here it's trying to get the method by using the Could we make a change to make it aware of what type of the property (accesstor or data) it's dealing with and get the method accordingly? I know that we could use the sinon.spy(obj, 'method', ['get']) right now but it would be nice if it would support without doing that since from Typescript 3.9+ they make any exported module a getter And when you're writing TS module you might forget that all of your module would be getters since TS does that behind the scene with transpilation Also it would make writing assertion simpler too (instead of sinon.assert.calledOnce(spy.get)) |
The Feel free to look into this, though! I would love if we could treat getters and values in the same way, but I am not entirely sure if it is feasible using the current APIs. You have the old stubs API and also the newer |
@fatso83 Thanks for the reply, I can try playing with it a bit! |
So annoying that we never reached closure on this one. There were too many suggestions in all kinds of directions. The original issue was perhaps never a real issue to begin with, when looking at it in hindsight:
AFAIK we have had that spy API all the time 🤔
What irks me is that we have different APIs for spies and stubs to deal with accessors. At least the consistency is better with the newer const o = {
get foo() {
return 100
},
set bar(value) {
this.value = value
},
}
const setter = sinon.fake(function (val) {
this.value = val * 10
})
sinon.replaceSetter(o, 'bar', setter)
const getter = sinon.fake.returns(42)
sinon.replaceGetter(o, 'foo', getter)
o.bar = 200
assert(o.value === 2000)
o.bar = 1
o.bar = 2
assert(setter.callCount === 3)
o.foo
assert(getter.callCount === 1) But that does not fix up the handle chainable APIs by itself. Auto-wrapping the replacements for the |
@kasamachenkaow I think #2531 and #2533 could improve on the situation, of informing users what is wrong, without doing anything too magical. |
What did you expect to happen?
I'm attempting to stub out - and monitor - a property getter,
using the stubbing approach recommended in the documentation at http://sinonjs.org/releases/v4.4.6/stubs/#stubgetgetterfn
I was hoping that the resulting stub would report when the getter was actually invoked, but no such reports are logged.
(In fact, I don't actually want to stub the getter: I merely want to spy on it, but Sinon provides no property getter spy API right now afaik.)
What actually happens
I can successfully stub out the getter, and control what it does, but Sinon's invocation recording reports no calls.
(This is also related to issue #1545.)
I do understand that, for a stubbed property, there are really two sets of function calls going on - the getter and the setter - and so really there should be two sets of recordings going on. But right now, there are none: when stubbing a property, as recommended in the docs above, no recording happens.
How to reproduce
There's a small example at https://codesandbox.io/s/l5m9loy21l; output is in the console.
The text was updated successfully, but these errors were encountered: