sinonjs / sinonjs/sinon

createStubInstance() doesn't receive per-sandbox callId context, breaking calledAfter/calledBefore vs spy()

Open
#2,760 0 comments 1 reaction 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
JavaScript
Stars
9.8k
Forks
809
PR merge metrics
No merged PRs in 30d

Description

Describe the bug

22.1.0 introduced per-sandbox callId isolation for parallel test support (#2472, fixed by #2715). Fakes created via sandbox.spy() / sandbox.stub() now get their callId from an isolated sandboxContext object via .withContext(...).

However, createStubInstance() (both the top-level sinon.createStubInstance() and sandbox.createStubInstance()) was not updated as part of that fix. It still calls the plain stub(stubInstance) internally (see create-stub-instance.js / stub.js), which never receives a context and therefore falls back to the hardcoded module-level singleton defaultContext = { callId: 0 } in proxy-invoke.js.

The practical effect: any fake created via createStubInstance() is tracked on a totally different callId counter than fakes created via spy() / stub(). Both counters start at 0 independently, so calledAfter() / calledBefore() / calledImmediatelyBefore() / calledImmediatelyAfter() comparisons between a createStubInstance()-created method and a spy()/stub()-created fake become meaningless — they no longer reflect real call order.

This worked correctly before 22.1.0, when there was a single global callId counter shared by everything.

To Reproduce

const sinon = require('sinon')

class Foo {
  bar () {}
}

const spy1 = sinon.spy(() => {})
spy1() // called first

const stubInst = sinon.createStubInstance(Foo)
stubInst.bar() // called second, strictly after spy1()

console.log(stubInst.bar.calledAfter(spy1)) // => false (should be true)
console.log('spy1 callIds:', spy1.callIds)         // [0]
console.log('stub.bar callIds:', stubInst.bar.callIds) // [0]

Both callIds are 0 even though stubInst.bar() ran strictly after spy1(), because they're tracked on two unrelated counter objects.

Expected behavior

stubInst.bar.calledAfter(spy1) should return true, matching real call order, the same as it did in sinon 22.0.0.

Context

  • Sinon version: 22.1.0
  • Runtime: Node.js (any)

Additional context

Likely fix: have createStubInstance() accept/forward a context argument the same way stub.withContext does, and have sandbox.createStubInstance() pass its own sandboxContext through, similar to how sandbox.spy() / sandbox.stub() already do.

I found this while investigating a test regression after bumping from sinon 22.0.0 to 22.1.0 — a test comparing call order between a sinon.createStubInstance()-based stub and a sinon.spy() started failing intermittently even though the real call order was correct. The repro above reduces it to the minimal case, with no project-specific code.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by tracing createStubInstance() through create-stub-instance.js and stub.js, then compare its context handling with sandbox.spy() and sandbox.stub(). Run the minimal reproduction from the issue and verify that createStubInstance()-created methods share call ordering with spies, including calledAfter(), calledBefore(), and related comparisons.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
testing-qa
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
72/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.