Skip to content

Support explicit resource management #2662

@sukima

Description

@sukima

Is your feature request related to a problem? Please describe.
I'm always frustrated when I have to remember to call sinon.restore() in test cleanup or risk memory leaks and test pollution. The current approach requires explicit cleanup in afterEach hooks or manual restoration, which is error-prone and leads to boilerplate code. With modern JavaScript's Explicit Resource Management (using/await using), we could leverage automatic cleanup at block scope end, making sandboxes potentially unnecessary for many use cases.

Describe the solution you'd like
I want all sinon primitives (spies, stubs, fakes, mocks, and sandboxes) to implement the Symbol.dispose method by default, enabling automatic cleanup when used with the using declaration. The disposal behavior should:

  • Individual objects: Dispose only the specific spy/stub/fake/mock (equivalent to calling .restore() on that instance)
  • Sandbox objects: Dispose all sinon objects within that sandbox (equivalent to calling sandbox.restore())
  • Multiple disposal calls: Be no-ops after the first call (idempotent behavior)
  • Backward compatibility: No breaking changes - existing code works unchanged, using keyword provides opt-in automatic cleanup

Example usage:

test('my test', function () {
  using myFake = sinon.replace(ob, 'method', sinon.fake());
  using mySpy = sinon.spy(obj, 'method');
  using sandbox = sinon.createSandbox();
  using stubFromSandbox = sandbox.stub(obj, 'otherMethod');
  
  doSomething();
  
  sinon.assert.called(myFake);
  // All objects automatically restored when leaving scope
  // sandbox disposal would also clean up stubFromSandbox
});

Describe alternatives you've considered

  • Manual sinon.restore() calls in afterEach hooks (current approach - verbose and error-prone)
  • Wrapper functions that return disposable objects (would require maintaining separate wrapper library)
  • Using try/finally blocks with manual cleanup (adds nesting and complexity)
  • Test framework-specific plugins (not portable across different test runners)
  • Custom DisposableStack implementations (more verbose than native using syntax)

Additional context
This feature would leverage the TC39 Explicit Resource Management proposal (Stage 3) which is already supported in TypeScript 5.2+ and modern JavaScript engines. The implementation would require adding [Symbol.dispose]() { this.restore(); } to relevant prototypes, with idempotent behavior to handle multiple disposal calls gracefully.

This approach could make sandboxes less necessary for many use cases, as individual using declarations provide the same automatic cleanup benefits with more granular control. It would make sinon more ergonomic and significantly reduce the likelihood of test pollution due to forgotten cleanup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions