sinonjs / sinon

Test spies, stubs and mocks for JavaScript.

Home Page:https://sinonjs.org/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

throws() keeps raising exception with callsFake()

ziluvatar opened this issue · comments

Describe the bug

When you create a stub like sinon.stub().throws(), if you later define another behavior for it with .callsFake(), while executing the call it will through the error instead of executing the fake call.

To Reproduce

const fn = sinon.stub().throws('not mocked');
fn.callsFake(() => 'mocked behavior')

console.log(fn())

Expected behavior
mocked behavior

Actual behavior

[...]/node_modules/sinon/lib/sinon/proxy-invoke.js:84
    throw exception;
    ^
not mocked
    at Object.fake.exceptionCreator ([...]/node_modules/sinon/lib/sinon/default-behaviors.js:18:32)
[...]

Context (please complete the following information):

  • Library version: 15.0.1
  • Environment: NodeJS v16.15.0

Additional context
N/A

Thanks for reminding me why we have tried moving people towards the Fake API 😄 Stub setup and testing is much harder to reason about than immutables. Like what is the correct behavior of setting up two unconditional and incompatible behaviors?

Also, the chaining API is fraught with peril. At first look, I thought this was a case of #2447, as you do not save the stub instance, but the result of the first behavior. That's worth knowing about at least, even though that was not the issue here.

Another issue is that I cannot see a use case for this that is not served better by more meaningful methods. If you want to have different behavior for different calls you could do this:

stub.onFirstCall().callsFake(() => "stubbed behavior");
stub.onSecondCall().throws("even more stubbed behavior");

Or if you want a default behavior, but changing it for some later invocation, you could use the on<X>Call family of methods or the withArgs() family, like:

stub.callsFake(() => "stubbed behavior"); // call 1, 3, 4
stub.onSecondCall().throws("throwing stub on call 2"); // call 2

Anyway ... I know that our current behavior is that subsequent (unconditional) stubbing should "overwrite"/shadow previous ones, so in that sense, aligning the throws behavior seems only reasonable to me. So definitely welcome the fix!

published as sinon 15.0.2

That was fast! Thanks!

I forgot to mention my specific use case. In my case, a module can receive different dependencies injected, while doing unit testing there is a set up function that creates stubs for all these dependencies, they are initialized with sinon.stub().throws(...). That way each specific test is forced to explicitly define (overwrite) the behavior of the needed dependencies for that specific case to perform the desired logic. If you forgot to define behavior for one for your case you'll see a nice error.

BTW thanks for the #2447 reference, I had that issue too and I assumed it was just not supported, I'll give a try next time without chaining.