Skip to content

fix: make return/throw behavior setters fully mutually exclusive - #2759

Open
rajanpanth wants to merge 1 commit into
sinonjs:mainfrom
rajanpanth:fix/behavior-setters-mutual-exclusion
Open

fix: make return/throw behavior setters fully mutually exclusive#2759
rajanpanth wants to merge 1 commit into
sinonjs:mainfrom
rajanpanth:fix/behavior-setters-mutual-exclusion

Conversation

@rajanpanth

Copy link
Copy Markdown

Summary

Each of returns/returnsArg/throwsArg/returnsThis/callsFake/resolves/resolvesArg/rejects/resolvesThis/callThrough/callThroughWithNew only clears the specific fields its author happened to think of, rather than the full set behavior.js#invoke checks. So e.g. returnsArg followed by throwsArg, callsFake, returnsThis, or resolves leaves returnArgAt in place, which invoke() checks before any of those — silently keeping the earlier behavior instead of being overridden:

const stub = sinon.stub();
stub.returnsArg(0);
stub.throwsArg(1);
stub("myarg", new Error("boom")); // returns "myarg" -- does NOT throw

#d36e921 fixed this for the returns-after-returnsArg pair specifically, but the same bug shape remained for every other combination.

Changes

  • src/sinon/default-behaviors.js: generalizes that fix into a shared resetBehavior(), called by every return/throw setter before it sets its own field(s). This is the same exhaustive-reset pattern callThrough already used — I just made every setter in this family use it consistently instead of each remembering to clear a handful of fields ad hoc.
    • Deliberately excludes the callsArg/yields callback-invocation fields from this reset: yielding a callback is a separate, additive side effect meant to combine with any of these return/throw behaviors regardless of call order (see the existing "returnsArg takes precedent over yielded return value" tests) — not another mutually-exclusive choice in this set. Left the callsArg/yields family of setters untouched.
    • callThrough/callThroughWithNew still additionally clear the callback config too, since "call the real method" is meant to replace all stub customization. This also fixes callThroughWithNew never having cleared returnArgAt/returnThis/fakeFn/resolve*/reject/returnValue, unlike callThrough (which already cleared everything).
  • test/src/stub-test.js: 6 new "is superseded by X" tests alongside the existing ones, covering the previously-broken combinations.

Testing

  • Full suite: 1503/1504 passing. The 1 failure (Sandbox > warns of potential leak when many fakes are created, a 2000ms timeout) is pre-existing and unrelated — confirmed via git stash, it fails identically without this change.
  • Confirmed all 6 new tests fail on the prior code and pass with the fix.
  • eslint --max-warnings 0 clean.

Each of returns/returnsArg/throwsArg/returnsThis/callsFake/resolves/
resolvesArg/rejects/resolvesThis/callThrough/callThroughWithNew only
cleared the specific fields its author happened to think of, rather
than the full set behavior.js#invoke checks. So e.g. returnsArg
followed by throwsArg, callsFake, returnsThis, or resolves left
returnArgAt in place, which invoke() checks before any of those --
silently keeping the earlier behavior instead of being overridden.

d36e921 fixed this for the returns-after-returnsArg pair specifically;
this generalizes that fix into a shared resetBehavior() called by
every return/throw setter, so a future setter can't reintroduce the
same class of bug by omission. Deliberately excludes the
callsArg/yields callback-invocation fields from that reset: yielding a
callback is a separate, additive side effect meant to combine with any
of these behaviors regardless of call order (see the existing
"returnsArg takes precedent over yielded return value" tests), not
another mutually-exclusive choice in this set. callThrough/
callThroughWithNew still clear the callback config too, since "call
the real method" is meant to replace all stub customization -- this
also fixes callThroughWithNew never having cleared returnArgAt/
returnThis/fakeFn/resolve*/reject/returnValue, unlike callThrough.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant