#2760·sinon

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

Author: rlsfCreated Aug 9, 2026Updated Aug 9, 2026

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

javascript
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.