Page MenuHomePhabricator

Upgrade from Sinon 1.x to Sinon 18
Closed, ResolvedPublic

Description

Notable changes

Notable changes from https://sinonjs.org/guides/migration-guide.html, based on our (limited) usage:

Sinon 2: sinon.useFakeXMLHttpRequest

This should be replaced with sandbox.useFakeServer (both exist in Sinon 1).

  • Difficulty: Easy

We already use useFakeServer() everywhere. Except one test in the UploadWizard extension (and a copy of that code, in MediaUploader) which oddly and redundantly calls both.

Sinon 2: Replace stub(obj, method, fn) with stub(ob, method).callsFake(fn)
  • Difficulty: Medium.

Trivial find-and-replace, but in a lot of places.

The bad news is that the replacement, callsFake, is introduced in the same version which means we can't migrate ahead of time. The good news is that while it emits warnings, the old way does still work in Sinon 2.x, so we can at least prepare for the bulk of our use by iterating in CI and then merging the long tail after the upgrade. The other bad news is that the old way is removed in Sinon 3.0, which means we have to do this in multiple stages. However, if this is the only hard milestone, we could patch the latest Sinon to temporarily monkey-patch this back in, so that we can do the migration in one step.

Sinon 3: Remove support for sandbox.stub(obj, 'nonExistingProperty')
  • Difficulty: TBD.

I vaguely recall using this in a few places to stub non-existent browser APIs, as well as to workaround the fact that Sinon doesn't permit stubbing of non-function values. This might not be an issue anymore, however.

Sinon 3: Replace sinon.useFakeTimers(now, ...prop) with sinon.useFakeTimers( { now, toFake } )
  • Difficulty: Medium.

The good news is that the vast majority of our usage is simply sinon.useFakeTimers() or sinon.useFakeTimers(now), which are unchanged.

The bad news is that this is yet another removal without deprecation, where the replacement is new in the same release, which means we can't prepare/migrate ahead of time. Where previously you'd call sinon.useFakeTimers(now, 'Date', 'setInterval') one must now call sinon.useFakeTimers({ now, toFake: ['Date'] }), where toFake is an array of date-related properties to globally.

The feature being deprecated here is imho an anti-pattern. The prop here refers to faking only a subset of date-related browser features. E.g. faking new Date but not setTimeout. I suspect a number of callers in our codebase are probably didn't know that this was an optional argument, or thought it was better to specify what you need, rather than an actual requirement or dependency to only part of the global clock progression. In any event, this will definitely need a monkey-patch since it skipped deprecation (unless there is e.g. only 1 caller and we force-merge that if CI passes in core with that patch applies).

Full list:
https://sinonjs.org/releases/v18/fake-timers/
https://github.com/sinonjs/fake-timers/tree/v13.0.5?tab=readme-ov-file#api-reference

["setTimeout", "clearTimeout", "setImmediate", "clearImmediate",
 "setInterval", "clearInterval", "Date",
 "requestAnimationFrame", "cancelAnimationFrame",
 "requestIdleCallback", "cancelIdleCallback",
 "hrtime", "performance"]
Sinon 4: (none)
Sinon 5: The sinon global is now itself a restorable sandbox.

This is not a breaking change, but a new feature, and quite a nice one.

Up until now, the main Sinon API was stateless so while you could call sinon.stub() any time and restore these individually, the would not be tracked anywhere as part of a group that you can verify and restore wholesale.

Instead, to automatically restore all stubs (as MediaWiki does after each test), we create and assign a sandbox group this.sandbox = sinon.sandbox.create();, followed by your test calling this.sandbox.stub(), and then eventually MediaWIki calls this.sandbox.restore().

After this upgrade,, we can use sinon.stub() directly in any QUnit test. MediaWiki will use the new global sinon.restore() after each test. Sinon still supports ad-hoc sandboxes all the same, this is an additional way to use it, and a much more ergonomic one.

Local sandbox (status quo)
QUnit.test('example', function (assert) {
  var x = this.sandbox.stub(thing, 'hello');
  // …
});
Manual (discouraged)
QUnit.test('example', function (assert) {
  var x = sinon.stub(thing, 'hello');
  // …
  x.restore();
});
Global sandbox (future)
QUnit.test('example', (assert) => {
  var x = sinon.stub(thing, 'hello');
  // …
});
Sinon 4: spy.reset() removed in favour of spy.resetHistory()
  • Difficulty: Medium.

Doesn't impact us much since we generally use sinon.stub() and not sinon.spy(). And even then while you may in rare cases want to .restore() a stub or spy mid-way a test, it is even more rare to do a partial restore by calling .reset().

Spies are a low-level concept in Sinon. Every stub uses a spy underneath. To reset a spy means to reset its call counters and argument history, which used to be spy.reset() and is now called spy.resetHistory(). To reset a stub means to reset its spy history and to reset any returnsValue behaviour overrides, which is unchanged and continues to be called stub.reset() although the second part of that is now also exposed as stub.resetBehaviour so stub.reset() is a shortcut for stub.resetHistory and stub.resetBehaviour. You can see how this is much cleaner, given that stub inherits spy, when these methods have their own name of this.

When using Codesearch to look for .reset() you're likely find cases that reset a stub, not a spy. So it's hard to find, but fortunately there aren't many.

Sinon 6 - Sinon 18: (none relevant)
Misc - Creating and replacing non-function properties

I haven't tried to narrow down when this unofficial feature changed, but it used to be possible to create or replace arbitrary properties such as plain objects, or even entire classes. Given that later Sinon versions specialise more into the function behaviors, this stopped working at some point, e.g. given var x = { a :1 }; something like sinon.stub( x, 'a', 2 ); is not valid.

The equivalent for this, is sinon.define(obj, prop, val) and sinon.replace(obj, prop, val).

Proposal

Plan A
  • Prep for Sinon 2 and Sinon 5:
    • Remove use of sinon.useFakeXMLHttpRequest.
    • Migrate handful of calls to sinon.reset().
  • Upgrade from Sinon 1.x to Sinon 18, with a monkey-patch that restores stub(obj, method, fn), spy.reset(), and useFakeTimers(now, prop).
  • Migrate stub(obj, method, fn) and useFakeTimers(now, prop).

Sentiment: Majority of code can migrate at ease after the upgrade, no breakage or disruption.

Plan B
  • Prep for Sinon 2: Remove use of sinon.useFakeXMLHttpRequest.
  • Upgrade from Sinon 1.x to Sinon 2
  • Migrate stub(obj, method, fn) to stub(ob, method).callsFake(fn).
  • Upgrade from Sinon 2 to Sinon 3, with a monkey-patch that restores useFakeTimers(now, prop)
  • Migrate useFakeTimers(now, prop).
  • Backport sinon.resetHistory atop Sinon 1.x, and migrate handful of calls to sinon.reset().
  • Upgrade from Sinon 3 to Sinon 18.

Sentiment: At three distinct points, tests in repos not updated in time will break.

Event Timeline

Change #1129591 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/core@master] qunit: Upgrade Sinon from 1.17.1 to 18.0.1

https://gerrit.wikimedia.org/r/1129591

Krinkle renamed this task from Upgrade from Sinon 1.x to a current version to Upgrade from Sinon 1.x to Sinon 18.Mar 20 2025, 2:55 AM

Change #1131885 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/GrowthExperiments@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1131885

Change #1131926 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/ArticlePlaceholder@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1131926

Change #1131885 merged by jenkins-bot:

[mediawiki/extensions/GrowthExperiments@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1131885

Change #1132014 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/Wikibase@master] ests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1132014

Change #1132017 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/AdvancedSearch@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1132017

Change #1132592 had a related patch set uploaded (by Thiemo Kreuz (WMDE); author: Thiemo Kreuz (WMDE)):

[mediawiki/extensions/AdvancedSearch@master] Remove not needed mw.msg() stubs from QUnit tests

https://gerrit.wikimedia.org/r/1132592

Change #1132017 merged by jenkins-bot:

[mediawiki/extensions/AdvancedSearch@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1132017

Change #1132592 merged by jenkins-bot:

[mediawiki/extensions/AdvancedSearch@master] tests: Remove not needed mw.msg() stubs from QUnit tests

https://gerrit.wikimedia.org/r/1132592

Change #1131926 merged by jenkins-bot:

[mediawiki/extensions/ArticlePlaceholder@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1131926

Change #1133066 had a related patch set uploaded (by Thiemo Kreuz (WMDE); author: Thiemo Kreuz (WMDE)):

[mediawiki/extensions/Cite@master] Remove not needed mw.msg stubs from QUnit tests

https://gerrit.wikimedia.org/r/1133066

Change #1133097 had a related patch set uploaded (by Thiemo Kreuz (WMDE); author: Thiemo Kreuz (WMDE)):

[mediawiki/extensions/ArticlePlaceholder@master] Remove not needed mw.msg stubs from QUnit tests

https://gerrit.wikimedia.org/r/1133097

Change #1132014 merged by jenkins-bot:

[mediawiki/extensions/Wikibase@master] tests: Use this.sandbox instead of sinon.sandbox.create()

https://gerrit.wikimedia.org/r/1132014

Change #1133066 merged by jenkins-bot:

[mediawiki/extensions/Cite@master] tests: Remove not needed mw.msg stubs

https://gerrit.wikimedia.org/r/1133066

Change #1133319 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/MediaUploader@master] tests: Remove redundant useFakeXMLHttpRequest()

https://gerrit.wikimedia.org/r/1133319

Change #1133320 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/UploadWizard@master] tests: Remove redundant useFakeXMLHttpRequest()

https://gerrit.wikimedia.org/r/1133320

Change #1133106 had a related patch set uploaded (by Thiemo Kreuz (WMDE); author: Thiemo Kreuz (WMDE)):

[mediawiki/extensions/MobileFrontend@master] tests: Remove not needed mw.msg stubs

https://gerrit.wikimedia.org/r/1133106

Change #1133320 merged by jenkins-bot:

[mediawiki/extensions/UploadWizard@master] tests: Remove redundant useFakeXMLHttpRequest()

https://gerrit.wikimedia.org/r/1133320

Change #1133106 merged by jenkins-bot:

[mediawiki/extensions/MobileFrontend@master] tests: Remove not needed mw.msg stubs

https://gerrit.wikimedia.org/r/1133106

Krinkle triaged this task as Medium priority.Apr 10 2025, 2:56 AM

Change #1129591 merged by jenkins-bot:

[mediawiki/core@master] qunit: Upgrade Sinon from 1.17.1 to 18.0.1

https://gerrit.wikimedia.org/r/1129591

Jdforrester-WMF claimed this task.

FWICT this is now fully Resolved? Nice work.

Change #1152283 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/EventLogging@master] qunit: Migrate deprecated sinon.useFakeTimers call

https://gerrit.wikimedia.org/r/1152283

Change #1152283 merged by jenkins-bot:

[mediawiki/extensions/EventLogging@master] qunit: Migrate deprecated sinon.useFakeTimers call

https://gerrit.wikimedia.org/r/1152283

Change #1188405 had a related patch set uploaded (by Hashar; author: Hashar):

[mediawiki/extensions/Wikibase@master] tests: fix QUnit tests without UniversalLanguageSelector

https://gerrit.wikimedia.org/r/1188405

Change #1188496 had a related patch set uploaded (by Krinkle; author: Krinkle):

[mediawiki/extensions/Wikibase@master] tests: Fix ULS stub to work even when ULS is not installed

https://gerrit.wikimedia.org/r/1188496

Change #1188405 abandoned by Hashar:

[mediawiki/extensions/Wikibase@master] tests: fix QUnit tests without UniversalLanguageSelector

https://gerrit.wikimedia.org/r/1188405

Change #1188496 merged by jenkins-bot:

[mediawiki/extensions/Wikibase@master] tests: Fix ULS stub to work even when ULS is not installed

https://gerrit.wikimedia.org/r/1188496