Skip to content
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

Stubbing a property getter works, but stub.called remains false #1545

Closed
OliverCole opened this issue Aug 28, 2017 · 13 comments
Closed

Stubbing a property getter works, but stub.called remains false #1545

OliverCole opened this issue Aug 28, 2017 · 13 comments
Labels
Documentation Property accessors Property Getters/Setters

Comments

@OliverCole
Copy link

Stubbing a property getter works, but stub.called remains false.

  • Sinon version : 3.2.1
  • Environment : Node 7
  • Other libraries you are using: None

What did you expect to happen?
Getting a stubbed property should set stub.called to true.

What actually happens
stub.called remains false

How to reproduce

var sinon = require('sinon');

var myObj = {
    prop: 'foo'
  };

var st = sinon.stub(myObj, 'prop').get(function getterFn() {
    return 'bar';
  });

console.log(myObj.prop);
console.log(st.called);
λ node testcase.js
bar
false
@fatso83
Copy link
Contributor

fatso83 commented Aug 28, 2017

This seems to have been like that forever. Not sure if this is a bug or not, as you are not actually calling the stub, but the getter set through the stub. And there are further complications, as you might see from this:

sinon.stub(myObj, 'prop').get(getterFunction).set(setterFunction);
myObj.prop = 1;
myObj.prop;
myObj.prop = 2;

The stub now has two methods. How would we go about this when trying to count callCount, called and friends? Would you mean the stub has been called 1 time, 2 times or 3 times?

@fatso83
Copy link
Contributor

fatso83 commented Aug 28, 2017

I'll close this as a non-bug, but we could perhaps need to clarify this in the docs. Feel free to update them.

@OliverCole
Copy link
Author

How would I mock/stub the value of the property, and check it was called?

@fatso83
Copy link
Contributor

fatso83 commented Aug 28, 2017

I am not sure if a "best" way, but one way (although inelegant) is to make the getter a stub too, and check on that. getterFn=sinon.stub().returns('baz')

@yongish
Copy link

yongish commented Nov 9, 2017

Hi, may I have some guidance on how to make this getter a stub?
I'm doing the following.
const createRPCThunk = sinon.stub(webRpcRedux, 'createRPCThunk');
console.log(createRPCThunk) is

{ [Function: proxy]
  isSinonProxy: true,
  formatters:
   { c: [Function: c],
     n: [Function: n],
     D: [Function: D],
     C: [Function: C],
     t: [Function: t],
     '*': [Function: *] },
  reset: [Function: reset],
  invoke: [Function: invoke],
  named: [Function: named],
  getCall: [Function: getCall],
  getCalls: [Function: getCalls],
  calledBefore: [Function: calledBefore],
  calledAfter: [Function: calledAfter],
  calledImmediatelyBefore: [Function: calledImmediatelyBefore],
  calledImmediatelyAfter: [Function: calledImmediatelyAfter],
  withArgs: [Function: withArgs],
  matchingFakes: [Function: matchingFakes],
  matches: [Function: matches],
  printf: [Function: printf],
  calledOn: [Function],
  alwaysCalledOn: [Function],
  calledWith: [Function],
  calledWithMatch: [Function],
  alwaysCalledWith: [Function],
  alwaysCalledWithMatch: [Function],
  calledWithExactly: [Function],
  alwaysCalledWithExactly: [Function],
  neverCalledWith: [Function],
  neverCalledWithMatch: [Function],
  threw: [Function],
  alwaysThrew: [Function],
  returned: [Function],
  alwaysReturned: [Function],
  calledWithNew: [Function],
  alwaysCalledWithNew: [Function],
  callArg: [Function],
  callArgWith: [Function],
  callArgOn: [Function],
  callArgOnWith: [Function],
  throwArg: [Function],
  yield: [Function],
  invokeCallback: [Function],
  yieldOn: [Function],
  yieldTo: [Function],
  yieldToOn: [Function],
  spyCall: { [Function: createSpyCall] toString: [Function: toString] },
  id: 'spy#0',
  called: false,
  notCalled: true,
  calledOnce: false,
  calledTwice: false,
  calledThrice: false,
  callCount: 0,
  firstCall: null,
  secondCall: null,
  thirdCall: null,
  lastCall: null,
  args: [],
  returnValues: [],
  thisValues: [],
  exceptions: [],
  callIds: [],
  errorsWithCallStack: [],
  displayName: 'stub',
  toString: [Function: toString],
  instantiateFake: [Function: create],
  func: { [Function: functionStub] id: 'stub#0' },
  createStubInstance: [Function],
  callsFake: [Function],
  callsArg: [Function],
  callsArgOn: [Function],
  callsArgWith: [Function],
  callsArgOnWith: [Function],
  usingPromise: [Function],
  yields: [Function],
  yieldsRight: [Function],
  yieldsOn: [Function],
  yieldsTo: [Function],
  yieldsToOn: [Function],
  throws: [Function],
  throwsException: [Function],
  returns: [Function],
  returnsArg: [Function],
  throwsArg: [Function],
  returnsThis: [Function],
  resolves: [Function],
  rejects: [Function],
  resolvesThis: [Function],
  callThrough: [Function],
  get: [Function],
  set: [Function],
  value: [Function],
  callsArgAsync: [Function],
  callsArgOnAsync: [Function],
  callsArgWithAsync: [Function],
  callsArgOnWithAsync: [Function],
  yieldsAsync: [Function],
  yieldsRightAsync: [Function],
  yieldsOnAsync: [Function],
  yieldsToAsync: [Function],
  yieldsToOnAsync: [Function],
  create: [Function: create],
  resetBehavior: [Function: resetBehavior],
  resetHistory: [Function: reset],
  onCall: [Function: onCall],
  onFirstCall: [Function: onFirstCall],
  onSecondCall: [Function: onSecondCall],
  onThirdCall: [Function: onThirdCall],
  isPresent: [Function],
  addBehavior: [Function],
  createBehavior: [Function],
  defaultBehavior: null,
  behaviors: [],
  rootObj:
   { createRPCActions: [Getter],
     getActionNamesForRPCId: [Getter],
     getActionNamesForRPCIds: [Getter],
     getActionNamesByType: [Getter],
     listActionNamesForRPCIds: [Getter],
     createRPCThunk: [Getter],
     rpcReducer: [Getter],
     isLoadingReducer: [Getter] },
  propName: 'createRPCThunk',
  restore: [Function: restore] }

How do I stub the getter of createRPCThunk?
I tried const createRPCThunk = sinon.stub().returns('baz');, const createRPCThunk = sinon.stub().returns((a, b, c, d) => 'baz');, and other variations, which are not working. Also createRPCThunk.getCall(0) is null.

@fatso83
Copy link
Contributor

fatso83 commented Nov 10, 2017

@yongish We are trying to keep the issue tracker focused on actual bugs. If you need help with something I suggest you try posting to StackOverflow (remember the sinon tag!) with a good description of what you want to achieve (very hard to understand now). The issue tracker isn't a help forum, but some of us monitor the sinon tag on Stack Overflow and try to answer if we have time.

@JakobJingleheimer
Copy link

For those trying to figure out how to do this, this thread hints at the solution:

const obj = {
    get a() { },
    set a() { },
};

const getterSpy = sinon.spy();
const setterSpy = sinon.spy();

sinon.stub(obj, 'a')
    .get(getterSpy)
    .set(setterSpy);

getterSpy.calledOnce; // false
obj.a;
getterSpy.calledOnce; // true

setterSpy.calledOnce; // false
obj.a = 'foo';
setterSpy.calledOnce; // true

I haven't tried spying on both the getter and setter at the same time, but I know the above works individually.

@RoystonS
Copy link

That's interesting. Presumably you can use sinon.stub() instead of spy there too (as it's not really spying on anything: no original methods get run)?

(Thinking aloud...)

In fact, if you were to write
const getterSpy = sinon.spy(obj, 'a'), would you get a real spy that actually spies on the original implementation?

@JakobJingleheimer
Copy link

JakobJingleheimer commented Apr 29, 2018

Presumably you can use sinon.stub() instead of spy there too

Yes, if you needed to actually return something, that does work:

const sandbox = createSandbox();
const ss = {};

ss.location = sandbox.stub(window, 'location').value({ search: '?' });
ss.search = sandbox.stub(window.location, 'search').value('?foo=bar');

window.location.search; // ?foo=bar
ss.search.value('?qux=zed');
window.location.search; // ?qux=zed

I couldn't get it to work by passing the sub-stub directly to the value of location:

ss.search = sandbox.stub().value('?foo=bar'); // error
ss.location = sandbox.stub(window, 'location').value({ search: ss.search });

The anon triggers an undefinable error (despite the docs saying anons are supported).

if you were to write const getterSpy = sinon.spy(obj, 'a')

@RoystonS, you would get an error that obj.a is not a function. If it didn't throw, the spy would be useless because obj.a is a property, so there's nothing for a spy to do.

Or, if you wrapped it in the sinon.spy after setting up the stub+spy, that would probably get an error about 'a' being undefined (because obj.a is now a proxy).

@DanKaplanSES
Copy link
Contributor

DanKaplanSES commented May 5, 2024

I think this might be officially documented now. Check the section named "Using a spy to wrap property getter and setter"

EDIT: I experimented with @JakobJingleheimer 's example and compared it to the official one, learning a lot in the process. This StackOverflow post goes into more detail.


@JakobJingleheimer
For those trying to figure out how to do this, this thread hints at the solution:

...
sinon.stub(obj, 'a')
    .get(getterSpy)
    .set(setterSpy);

getterSpy.calledOnce; // false
obj.a;
getterSpy.calledOnce; // true

setterSpy.calledOnce; // false
obj.a = 'foo';
setterSpy.calledOnce; // true

Why doesn't this work when you give the stub a name and use it in your assertions instead? i.e.,:

...
const stubProperty = sinon.stub(obj, 'a')
    .get(getterSpy)
    .set(setterSpy);

stubProperty.calledOnce; // false
obj.a;
stubProperty.calledOnce; // always false

stubProperty.calledOnce; // false
obj.a = 'foo';
stubProperty.calledOnce; // always false

It would make more sense to me if stubProperty had a callCount > 1, but it is always 0. Why is that the case?

@JakobJingleheimer
Copy link

Um, it's been 6 years.

I presume the problem is how you're accessing it (indirectly instead of the spy/stub itself).

@DanKaplanSES
Copy link
Contributor

DanKaplanSES commented May 6, 2024

Um, it's been 6 years.

Sorry, I didn't communicate this well, but I was throwing that out into the ether for anyone to answer.

I presume the problem is how you're accessing it (indirectly instead of the spy/stub itself).

I think so too. Maybe I'm mistaken, but with stubbed functions/methods, I don't recall this posing a problem; it feels like stubbing properties makes the sinon API work differently.

Either way, I experimented with your example and learned a lot in the process. This StackOverflow post goes into more detail.

@fatso83
Copy link
Contributor

fatso83 commented May 6, 2024

const getterSpy = sinon.spy();
const setterSpy = sinon.spy();

const obj = {
    a: 42,
};

const stubProperty = sinon.stub(obj, "a").get(getterSpy).set(setterSpy);

const b = obj.a;
const c = obj.a;
obj.a = 43;

console.log(getterSpy.callCount); // 2
console.log(setterSpy.callCount); // 1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Documentation Property accessors Property Getters/Setters
Projects
None yet
Development

No branches or pull requests

6 participants