Skip to content

Commit b67d307

Browse files
authored
feat: Explicit Resource Management support in mocked functions (#7927)
1 parent e84e218 commit b67d307

File tree

3 files changed

+42
-1
lines changed

3 files changed

+42
-1
lines changed

docs/api/vi.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,19 @@ expect(spy).toHaveBeenCalled()
459459
expect(spy).toHaveReturnedWith(1)
460460
```
461461

462+
::: tip
463+
In environments that support [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management), you can use `using` instead of `const` to automatically call `mockRestore` on any mocked function when the containing block is exited. This is especially useful for spied methods:
464+
465+
```ts
466+
it('calls console.log', () => {
467+
using spy = vi.spyOn(console, 'log').mockImplementation(() => {})
468+
debug('message')
469+
expect(spy).toHaveBeenCalled()
470+
})
471+
// console.log is restored here
472+
```
473+
:::
474+
462475
::: tip
463476
You can call [`vi.restoreAllMocks`](#vi-restoreallmocks) inside [`afterEach`](/api/#aftereach) (or enable [`test.restoreMocks`](/config/#restoreMocks)) to restore all methods to their original implementations. This will restore the original [object descriptor](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty), so you won't be able to change method's implementation:
464477

packages/spy/src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ Jest uses the latter for `MockInstance.mockImplementation` etc... and it allows
175175
const boolFn: Jest.Mock<() => boolean> = jest.fn<() => true>(() => true)
176176
*/
177177
/* eslint-disable ts/method-signature-style */
178-
export interface MockInstance<T extends Procedure = Procedure> {
178+
export interface MockInstance<T extends Procedure = Procedure> extends Disposable {
179179
/**
180180
* Use it to return the name assigned to the mock with the `.mockName(name)` method. By default, it will return `vi.fn()`.
181181
* @see https://vitest.dev/api/mock#getmockname
@@ -549,6 +549,10 @@ function enhanceSpy<T extends Procedure>(
549549
return stub
550550
}
551551

552+
if (Symbol.dispose) {
553+
stub[Symbol.dispose] = () => stub.mockRestore()
554+
}
555+
552556
stub.getMockImplementation = () =>
553557
implementationChangedTemporarily ? implementation : (onceImplementations.at(0) || implementation)
554558
stub.mockImplementation = (fn: T) => {

test/core/test/jest-mock.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,30 @@ describe('jest mock compat layer', () => {
560560
expect(fn.getMockImplementation()).toBe(temporaryMockImplementation)
561561
})
562562

563+
describe('is disposable', () => {
564+
describe.runIf(Symbol.dispose)('in environments supporting it', () => {
565+
it('has dispose property', () => {
566+
expect(vi.fn()[Symbol.dispose]).toBeTypeOf('function')
567+
})
568+
it('calls mockRestore when disposing', () => {
569+
const fn = vi.fn()
570+
const restoreSpy = vi.spyOn(fn, 'mockRestore')
571+
{
572+
using _fn2 = fn
573+
}
574+
expect(restoreSpy).toHaveBeenCalled()
575+
})
576+
it('allows disposal when using mockImplementation', () => {
577+
expect(vi.fn().mockImplementation(() => {})[Symbol.dispose]).toBeTypeOf('function')
578+
})
579+
})
580+
describe.skipIf(Symbol.dispose)('in environments not supporting it', () => {
581+
it('does not have dispose property', () => {
582+
expect(vi.fn()[Symbol.dispose]).toBeUndefined()
583+
})
584+
})
585+
})
586+
563587
describe('docs example', () => {
564588
it('mockClear', () => {
565589
const person = {

0 commit comments

Comments
 (0)